Sequential Read
通过分段阅读并生成结构化反思,模拟真实阅读体验,记录认知变化过程。
将网页文章和文件保存至个人知识库,支持搜索与分析。
openclaw skills install @zinklu/puzle-read命令、参数、文件名以原文为准
将网页文章、文档和文件保存到由 Puzle 驱动的个人阅读库中。
AI 将分析全文内容,支持对您已读内容进行语义搜索。
当此技能首次激活时,请立即向用户打招呼,并引导其完成设置:
from puzle_reading import PuzleReadingClient
if PuzleReadingClient.token_is_configured():
# 令牌已存在,准备就绪如果已配置,请告知用户:
“Puzle 读取技能已准备就绪!您可以发送任意网址、文件或文本,我将将其保存至您的阅读库。您还可以对所有已读内容进行搜索。”
- 告知用户:“为了开始使用,我需要连接到您的 Puzle 账户。”
- 请用户在浏览器中打开 https://read-web.puzle.com.cn/device-auth 并登录。
- 页面会显示一个 设备授权码 —— 请用户复制并粘贴该代码返回。
- 用户提供代码后,将其兑换为令牌:
PuzleReadingClient.exchange_device_code(code)
# 令牌将内部保存 —— 永不返回或暴露- “现在您可以发送任意网址、文章、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 <命令> [选项]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 列表,用于限制搜索范围# 首次授权
./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(仅所有者可读写)。
唯一 获取令牌的方式是通过设备授权码交换流程。
令牌保密规则:
eyJ... 开头的 JWT,应拒绝并引导其通过设备授权流程完成连接_save_token / _load_token 为私有方法)引导用户完成设备授权:
PuzleReadingClient.exchange_device_code(code)
# 令牌将内部保存 —— 永不返回或暴露
client = PuzleReadingClient() # 现在将自动加载已保存的令牌注意: 授权码是短期且一次性使用的——用户可以安全地将其粘贴到对话中。实际的 JWT 令牌不会出现在聊天记录中。
一旦令牌被保存,后续所有调用将自动生效——无需再次向用户请求:
client = PuzleReadingClient() # 自动从 ~/.config/puzle/config.json 读取你也可以提前检查:
if not PuzleReadingClient.token_is_configured():
# 引导用户完成设备授权流程
...令牌的有效期约为 7 天。当收到 PuzleAPIError(code=401) 时,请执行以下步骤:
PuzleReadingClient.exchange_device_code(code)当用户仅希望 保存 内容时——无需等待处理结果。创建阅读任务,提供网页链接,即可完成。
result = client.create_reading_from_url("https://example.com/article")
# result.web_url → "https://read.puzle.com.cn/read/42"
# 告诉用户:「已保存!你可以在这里查看:{result.web_url}」适用于以下场景:
当用户希望 理解、总结或使用 内容时,处理时间需 30–90 秒。为避免阻塞对话:
wait_for_reading() + 分析# 步骤 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 秒。在子代理或后台进程中运行可保持对话流畅。用户能立即获得网页链接,可开始阅读,而分析在后台并行进行。
适用于以下场景:
当用户发送一个链接时,**不要直接调用 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
此技能用于将网页、HTML 内容或本地文件转换为可持久化存储的阅读记录(Reading),支持从 URL、HTML 或本地文件创建,并提供搜索、列表和详情查询功能。
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),请始终将该链接分享给用户。
result = client.create_reading_from_url("https://example.com/article")
print(result.web_url) # → "https://read.puzle.com.cn/read/42"适用于内容已提前抓取的情况,跳过抓取阶段,处理更快。
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)支持格式: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 上传。
若用户提供的是纯文本、私密文档或其他文本材料,可先保存为本地文件,再通过此方法创建阅读。
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 会自动路由至正确接口。
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"] — 所有页的总数量返回结果包含链接与文件类阅读,按最新时间排序。
# 在所有阅读中搜索
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_id、reading_title、resource_type、chunk_text、score。
result = client.create_reading_from_url("https://example.com/article")
print(f"已保存:{result.web_url}")result = client.create_reading_from_url("https://example.com/interesting-article")
# 告诉用户:“已保存!你可以在这里查看:{result.web_url}”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)# 步骤 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"]
# 生成摘要,待准备就绪后向用户展示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"]
# 分析内容,然后向用户展示结果当用户输入原始文本(如笔记、备忘录等)时,需先将其保存为临时文件:
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}成功响应的 code 为 200,并包含 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 秒 | 建议稍后重试;部分大文件可能需要更长时间 |
wait_for_reading()(模式 B)page_size 最大值为 100(用于 list_readings)上传的文件(PDF、图片、音频等)和网页文章内容将被发送至 Puzle 服务进行处理与存储。在代表用户上传前,请告知他们:
https://read-web.puzle.com.cn,不可更改已收录 1 个 Skill