puzle-read

将网页文章和文件保存至个人知识库,支持搜索与分析。

已扫描
适合谁
知识管理者、研究者与学生
不适合谁
需要本地完全离线存储的用户、对数据隐私要求极高的敏感行业用户
国内可用性
需网络配置。可能需要网络配置或第三方服务可访问。
安装难度
新手友好(★☆☆)。基于终端操作、依赖、API Key 和本地环境要求的初步判断。

安装与下载

openclaw skills install @zinklu/puzle-read

Skill 说明

命令、参数、文件名以原文为准

Puzle 读取技能

将网页文章、文档和文件保存到由 Puzle 驱动的个人阅读库中。

AI 将分析全文内容,支持对您已读内容进行语义搜索。

首次设置(在技能安装时显示)

当此技能首次激活时,请立即向用户打招呼,并引导其完成设置:

  1. 检查是否已配置令牌
   from puzle_reading import PuzleReadingClient
   if PuzleReadingClient.token_is_configured():
       # 令牌已存在,准备就绪

如果已配置,请告知用户:

“Puzle 读取技能已准备就绪!您可以发送任意网址、文件或文本,我将将其保存至您的阅读库。您还可以对所有已读内容进行搜索。”

  1. 若未配置令牌,请引导用户完成设备授权流程:

- 告知用户:“为了开始使用,我需要连接到您的 Puzle 账户。”

- 请用户在浏览器中打开 https://read-web.puzle.com.cn/device-auth 并登录。

- 页面会显示一个 设备授权码 —— 请用户复制并粘贴该代码返回。

- 用户提供代码后,将其兑换为令牌:

     PuzleReadingClient.exchange_device_code(code)
     # 令牌将内部保存 —— 永不返回或暴露
  • 确认用户: “已成功连接至 Puzle!”
  1. 设置完成后,简要说明该技能的功能:

- “现在您可以发送任意网址、文章、PDF 或文本,我将把它们保存到您的 Puzle 阅读库中。我还能为您总结文章、跨阅读内容搜索等。试试发送一个链接吧!”

先决条件

内置的客户端 SDK 位于此技能目录下的 scripts/puzle_reading.py

它依赖 requests 库,既可作为 Python 库使用,也可作为独立命令行工具。

作为 Python 库使用:

import sys
sys.path.insert(0, "<此技能目录>/scripts")
from puzle_reading import PuzleReadingClient

作为命令行工具使用:

python3 <此技能目录>/scripts/puzle_reading.py <命令> [选项]

CLI 参考

SDK 同时支持命令行工具。所有数据命令输出 JSON 到标准输出;错误信息输出到标准错误。

puzle_reading <命令> [选项]

命令:
  auth <code>             使用设备授权码换取令牌
                          (在 https://read-web.puzle.com.cn/device-auth 获取)
  status                  检查是否已配置有效令牌
  save-url <url>          从网址创建一篇阅读内容
  save-file <路径>        从本地文件创建一篇阅读内容
                          (支持格式:PDF、TXT、MD、CSV、JPG、PNG、WebP、GIF、MP3、WAV)
  save-html               从预获取的 HTML 内容创建一篇阅读内容
    --url URL             原始网址(必需)
    --title 标题          文章标题(必需)
    --content 内容        正文 HTML 字符串,或以 "@路径" 表示从文件读取(必需)
    --text-content 文本    纯文本版本,或以 "@路径" 表示从文件读取(必需)
    --excerpt 摘要        简短摘要
    --byline 作者         作者姓名
    --site-name 站点名     站点名称
    --published-time 时间  ISO 时间字符串
  list                    列出阅读库中的全部阅读内容
    --page N              页码(默认:1)
    --page-size N         每页条目数(默认:10,最大:100)
  detail <id> <类型>      获取某篇阅读内容的详细信息(类型:link | file)
  wait <id> <类型>        等待处理完成,然后打印详细信息
    --timeout 秒数        最大等待时间(默认:120)
    --interval 秒数       轮询间隔(默认:3)
  search <查询>           在您的阅读内容中进行语义搜索
    --top-k 数量          最多返回结果数量(默认:5)
    --reading-ids ID列表  用逗号分隔的阅读 ID 列表,用于限制搜索范围

CLI 示例

# 首次授权
./puzle_reading.py auth "abc123-device-code"

# 检查认证状态
./puzle_reading.py status

# 保存一个网址
./puzle_reading.py save-url "https://example.com/article"

# 上传一个 PDF 文件
./puzle_reading.py save-file ~/Documents/paper.pdf

# 保存预获取的 HTML 内容(内容来自文件)
./puzle_reading.py save-html \
  --url "https://example.com/article" \
  --title "文章标题" \
  --content @/tmp/body.html \
  --text-content @/tmp/body.txt

# 列出最近的阅读内容
./puzle_reading.py list --page 1 --page-size 20

# 获取特定阅读内容的详情
./puzle_reading.py detail 42 link

# 等待处理完成
./puzle_reading.py wait 42 link --timeout 90

# 在所有阅读内容中搜索
./puzle_reading.py search "注意力机制" --top-k 10

# 在指定阅读内容中搜索
./puzle_reading.py search "Transformer" --reading-ids 1,2,3

令牌管理

令牌存储于 ~/.config/puzle/config.json,文件权限为 0o600(仅所有者可读写)。

唯一 获取令牌的方式是通过设备授权码交换流程。

令牌保密规则:

  • 绝不以任何形式输出、显示、记录或回显 JWT 令牌
  • 绝不接受用户提供的原始 JWT 令牌 —— 若用户粘贴了以 eyJ... 开头的 JWT,应拒绝并引导其通过设备授权流程完成连接
  • 绝不将令牌设置为环境变量
  • 令牌由 SDK 内部统一管理(_save_token / _load_token 为私有方法)

用户需授权(无令牌或令牌已过期)

引导用户完成设备授权:

  1. 请用户打开 https://read-web.puzle.com.cn/device-auth 并登录
  2. 页面显示一个 设备授权码 —— 请用户将该代码粘贴返回
  3. 用该代码换取令牌:
   PuzleReadingClient.exchange_device_code(code)
   # 令牌将内部保存 —— 永不返回或暴露
   client = PuzleReadingClient()  # 现在将自动加载已保存的令牌
  1. 确认用户:“已成功连接至 Puzle!”

注意: 授权码是短期且一次性使用的——用户可以安全地将其粘贴到对话中。实际的 JWT 令牌不会出现在聊天记录中。

已配置令牌(最常见情况)

一旦令牌被保存,后续所有调用将自动生效——无需再次向用户请求:

client = PuzleReadingClient()  # 自动从 ~/.config/puzle/config.json 读取

你也可以提前检查:

if not PuzleReadingClient.token_is_configured():
    # 引导用户完成设备授权流程
    ...

令牌已过期(401 错误)

令牌的有效期约为 7 天。当收到 PuzleAPIError(code=401) 时,请执行以下步骤:

  1. 告知用户其令牌已过期
  2. 引导用户重新完成设备授权流程(参见上文“用户需要授权”部分)
  3. 交换新代码:PuzleReadingClient.exchange_device_code(code)

使用的两种模式

模式 A:稍后阅读(“稍后阅读”)

当用户仅希望 保存 内容时——无需等待处理结果。创建阅读任务,提供网页链接,即可完成。

result = client.create_reading_from_url("https://example.com/article")
# result.web_url → "https://read.puzle.com.cn/read/42"
# 告诉用户:「已保存!你可以在这里查看:{result.web_url}」

适用于以下场景:

  • “保存这个” / “收藏” / “稍后阅读” / “存储这个链接”
  • “保存全部这些”——批量保存多个链接
  • 用户上传文件但未要求分析——“只存这个 PDF”
  • 用户随意分享链接,未就内容提出问题
  • “留着以后看”

模式 B:立即分析(后台处理)

当用户希望 理解、总结或使用 内容时,处理时间需 30–90 秒。为避免阻塞对话:

  1. 创建阅读任务并立即返回网页链接
  2. 启动后台任务 / 子代理 执行 wait_for_reading() + 分析
  3. 后台任务完成后,向用户展示结果
# 步骤 1:创建并立即响应用户
result = client.create_reading_from_url("https://example.com/article")
# 告诉用户:「正在处理。你可以在这里预览:{result.web_url}」

# 步骤 2:在后台任务 / 子代理中等待并分析
detail = client.wait_for_reading(result.reading_id, result.resource_type)
content = detail["data"]["content"]
# 基于内容生成摘要或回答问题

# 步骤 3:完成后向用户展示结果

为何采用后台处理? wait_for_reading() 会阻塞 30–90 秒。在子代理或后台进程中运行可保持对话流畅。用户能立即获得网页链接,可开始阅读,而分析在后台并行进行。

适用于以下场景:

  • “这篇文章说了什么” / “请总结一下” / “给我一个摘要”
  • “这篇 PDF 的关键发现是什么”——需要阅读并分析
  • “比较这两篇文章的观点”——需要多个阅读内容
  • 用户针对文章内容提出具体问题
  • 后续需要跨阅读内容进行搜索

何时使用哪种方法

用户分享链接 —— 决策流程

当用户发送一个链接时,**不要直接调用 create_reading_from_url()**。应按以下顺序尝试,仅在失败时进入下一步:

用户发送一个链接
  │
  ▼
┌─────────────────────────────────────────────────┐
│ 步骤 1:自行获取页面内容                        │
│                                                 │
│ 使用 WebFetch、浏览器或其他可用工具获取页面内容。│
│                                                 │
│ 获取到有效内容?                                │
│   → 使用 readability 提取内容(或直接使用工具输出)│
│   → 提取:标题、正文 HTML、纯文本、作者、网站名称 │
│   → 调用 create_reading_from_html()             │
│   ✅ 完成                                       │
│                                                 │
│ 此方法的优势:                                  │
│ - 跳过 Puzle 的抓取阶段,更快                    │
│ - 可处理 JS 渲染的动态页面                      │
│ - 可应对反爬虫措施                              │
│ - 内容质量更高(可预先处理)                    │
└────────────┬────────────────────────────────────┘
             │ 失败:内容为空、401/403 错误、登录墙、需认证、超时
             ▼
┌─────────────────────────────────────────────────┐
│ 步骤 2:检查是否有专用工具                      │
│                                                 │
│ 该链接可能是内部或受保护资源。检查环境中是否存在可访问的工具:│
│ - 内部文档系统的 MCP 服务器(Confluence、Notion、Google Docs 等)│
│ - 可获取内部内容的 API 接口                     │
│ - 已认证的浏览器会话                            │
│                                                 │
│ 找到可用工具并获取内容?                        │
│   → 将内容保存为本地文件(.html / .md)         │
│   → 调用 create_reading_from_file()             │
│   ✅ 完成                                       │
└────────────┬────────────────────────────────────┘
             │ 无合适工具,或仍失败
             ▼
┌─────────────────────────────────────────────────┐
│ 步骤 3:降级处理——交由 Puzle 服务器抓取          │
│                                                 │
│ 调用 create_reading_from_url(url)               │
│ Puzle 后端负责抓取和解析。即使对受保护页面也可能失败,但这是最后手段。│
│ ✅ 完成                                         │
└─────────────────────────────────────────────────┘

步骤 1 readability 提取示例

markdown

Puzle 读取技能 (puzle-read)

概述

此技能用于将网页、HTML 内容或本地文件转换为可持久化存储的阅读记录(Reading),支持从 URL、HTML 或本地文件创建,并提供搜索、列表和详情查询功能。


使用场景与流程

何时跳过前两步直接进入第 3 步?

  • 用户明确表示“让 Puzle 自动获取”或“使用 URL 方式”
  • 批量保存多个链接时(效率优先,无需逐个单独抓取)

用户直接提供内容 — create_reading_from_html()

用户表达示例
粘贴原始 HTML 或文章文本“分析这段内容:...”
提供已抓取的页面内容“我已经爬取了这个页面”
来自 JS 渲染的页面内容“这个页面是 JS 渲染的,这是源码”

用户提供本地文件 — create_reading_from_file()

用户表达示例
上传或引用本地文件“分析这个 PDF” / “读一下这份报告”
分享会议笔记、备忘录、私密文档“这是今天会议的笔记,帮我保存”
提供纯文本以持久化存储“这些是我的笔记” — 可先保存为临时 .md 文件
分享图片用于分析“看看这张截图”(JPG/PNG)
分享音频用于转录“处理这段录音”(MP3/WAV)
内部链接内容已提前获取第一步中已提取的内容,保存为文件后上传

search() — 用户希望在已有阅读中查找内容

用户表达示例
记得之前读过相关内容“我之前读过一篇关于微服务的文章”
寻找特定概念“我的知识库中有提到注意力机制的内容吗?”
跨主题交叉引用“找出所有提及 transformer 的内容”
事实核查“我记得某篇文章提到了 X,帮我找出来”
基于已有知识继续深入“那篇论文到底说了什么?”

list_readings() — 用户希望浏览自己的阅读集合

用户表达示例
查看最近阅读记录“我最近读了哪些内容?” / “展示我的阅读历史”
查找特定标题的阅读“前几天保存的那篇文章叫什么名字?”
整理或回顾收藏“列出我的所有阅读记录”

get_reading_detail() — 需要获取已知阅读的详细内容

用户表达示例
想重新阅读某篇文章“显示编号 #42 的内容”
继续跟进之前的阅读“把那篇文章的全文给我”
检查处理是否完成(内部使用,在创建阅读后调用)

核心概念

概念说明
Reading在 Puzle 中的单条内容记录,可由 URL、HTML 或文件创建
resource_type"link"(URL/HTML 来源)或 "file"(上传文件)
状态流转fetching → parsing → ai_reading → done / fail
处理机制创建阅读会立即返回结果;若需即时获取完整内容,请使用 wait_for_reading()

可用方法

所有 create_reading_* 方法均返回 ReadingResult 对象,包含 web_url 字段(如 https://read.puzle.com.cn/read/42),请始终将该链接分享给用户。


1. 从 URL 创建阅读

result = client.create_reading_from_url("https://example.com/article")
print(result.web_url)  # → "https://read.puzle.com.cn/read/42"

2. 从 HTML(预抓取内容)创建阅读

适用于内容已提前抓取的情况,跳过抓取阶段,处理更快。

result = client.create_reading_from_html(
    url="https://example.com/article",
    title="文章标题",
    content="<div>原始 HTML...</div>",
    text_content="纯文本版本...",
    # 可选参数:excerpt, byline, site_name, published_time
)
detail = client.wait_for_reading(result.reading_id, result.resource_type)

3. 从本地文件创建阅读

支持格式:PDF、TXT、MD、CSV、JPG、PNG、WebP、GIF、MP3、WAV

result = client.create_reading_from_file("/path/to/document.pdf")
detail = client.wait_for_reading(result.reading_id, result.resource_type)

SDK 内部自动处理 MD5 校验与 S3 上传。

若用户提供的是纯文本、私密文档或其他文本材料,可先保存为本地文件,再通过此方法创建阅读。


4. 获取阅读详情

detail = client.get_reading_detail(reading_id=42, resource_type="link")
# detail["data"]["status"]  — 检查处理状态
# detail["data"]["content"] — 完整 HTML(状态为 "done" 时可用)
# detail["data"]["title"]   — 提取的标题

使用 resource_type="file" 查询文件类阅读。SDK 会自动路由至正确接口。


5. 列出所有阅读记录

readings = client.list_readings(page=1, page_size=10)
for item in readings["data"]["items"]:
    print(f"[{item['resource_type']}] {item['title']} — {item['status']}")
# readings["data"]["total"] — 所有页的总数量

返回结果包含链接与文件类阅读,按最新时间排序。


6. 语义搜索

# 在所有阅读中搜索
hits = client.search("NLP 中的注意力机制", top_k=5)

# 仅在指定阅读中搜索
hits = client.search("Transformer 架构", reading_ids=[1, 2, 3])

for hit in hits["data"]["items"]:
    print(f"[{hit['reading_title']}] (得分: {hit['score']:.3f})")
    print(f"  {hit['chunk_text'][:200]}...")

每条结果包含:reading_idreading_titleresource_typechunk_textscore


工作流示例

保存链接以备后续阅读(模式 A)

result = client.create_reading_from_url("https://example.com/article")
print(f"已保存:{result.web_url}")

从网页链接创建阅读内容(模式 A)

result = client.create_reading_from_url("https://example.com/interesting-article")
# 告诉用户:“已保存!你可以在这里查看:{result.web_url}”

批量保存多个链接(模式 A)

urls = ["https://article1.com", "https://article2.com", "https://article3.com"]
results = [client.create_reading_from_url(url) for url in urls]
# 告诉用户:“已将全部 3 篇文章保存至你的阅读库:”
# for r in results: print(r.web_url)

摘要一篇文章(模式 B — 后台处理)

# 步骤 1:立即创建并响应
result = client.create_reading_from_url("https://arxiv.org/abs/1706.03762")
# 告诉用户:“正在分析文章。你可以在此预览:{result.web_url}”

# 步骤 2:在后台任务 / 子代理中执行
detail = client.wait_for_reading(result.reading_id, result.resource_type)
content = detail["data"]["content"]
# 生成摘要,待准备就绪后向用户展示

上传并分析本地 PDF 文件(模式 B — 后台处理)

result = client.create_reading_from_file("~/Documents/research-paper.pdf")
# 告诉用户:“文件已上传,正在处理:{result.web_url}”

# 在后台任务 / 子代理中执行
detail = client.wait_for_reading(result.reading_id, result.resource_type)
content = detail["data"]["content"]
# 分析内容,然后向用户展示结果

将用户提供的文本保存为阅读条目(模式 A)

当用户输入原始文本(如笔记、备忘录等)时,需先将其保存为临时文件:

import tempfile
with tempfile.NamedTemporaryFile(mode="w", suffix=".md", delete=False) as f:
    f.write(user_provided_text)
    temp_path = f.name
result = client.create_reading_from_file(temp_path)
# 告诉用户:“已保存至你的阅读库:{result.web_url}”

在所有阅读内容中搜索

hits = client.search("attention mechanism", top_k=5)
for hit in hits["data"]["items"]:
    print(f"[{hit['reading_title']}] {hit['chunk_text'][:100]}...")

查找并重新阅读过去的文章

readings = client.list_readings(page=1, page_size=20)
# 向用户展示标题以便选择
# 然后获取完整内容:
detail = client.get_reading_detail(reading_id=42, resource_type="link")
content = detail["data"]["content"]

错误处理

API 响应格式: Puzle 后端始终返回 HTTP 200。错误通过 JSON 响应体中的 code 字段标识,错误信息在 msg 中:

{"code": 401002, "msg": "无效或过期的设备码", "timestamp": 1775111299}

成功响应的 code200,并包含 data 字段。

SDK 对所有 API 错误抛出 PuzleAPIError 异常,该异常带有 .code.msg 属性。

错误含义应对措施
PuzleAPIError(code=401_002)无效或过期的设备码提示用户前往 /device-auth 获取新码
PuzleAPIError(code=401_001)JWT 令牌已过期引导用户通过 https://read-web.puzle.com.cn/device-auth 重新完成设备认证
PuzleAPIError(code=403_003)游客用户使用上限已超建议用户升级 Puzle 账户
PuzleAPIError(code=404_001)未找到阅读条目验证 reading_id 是否正确
PuzleAPIError(code=500) 在等待过程中发生处理失败告知用户内容无法被处理
PuzleTimeoutError处理耗时超过 120 秒建议稍后重试;部分大文件可能需要更长时间

使用限制

  • 内容处理耗时 30–90 秒 —— 仅在需要内容时调用 wait_for_reading()(模式 B)
  • JWT 令牌每 7 天 过期一次 —— 令牌过期后需通过设备认证重新授权
  • 文件上传链接有效期为 1 小时
  • page_size 最大值为 100(用于 list_readings)

数据隐私

上传的文件(PDF、图片、音频等)和网页文章内容将被发送至 Puzle 服务进行处理与存储。在代表用户上传前,请告知他们:

  • 内容将被传输并存储于 Puzle 服务
  • 用户不应上传敏感、机密或受监管的数据,除非已确认服务的隐私政策与数据保留策略
  • 服务地址硬编码为 https://read-web.puzle.com.cn,不可更改
Z
@zinklu

已收录 1 个 Skill

相关推荐