Puzle Read Skill
Save web articles, documents and files to your personal reading library powered by Puzle.
AI analyzes the full text and enables semantic search across everything you've read.
First-time Setup (show this on skill install)
When this skill is first activated, immediately greet the user and guide them through setup:
- 1. Check if a token is already configured:
from puzle_reading import PuzleReadingClient
if PuzleReadingClient.token_is_configured():
# Token exists, ready to go
If yes, tell the user: "Puzle Read Skill is ready! You can send me any URL, file, or text
and I'll save it to your reading library. You can also search across everything you've read."
- 2. If no token is configured, walk the user through device authorization:
- Tell the user: "To get started, I need to connect to your Puzle account."
- Ask the user to open
https://read-web.puzle.com.cn/device-auth in their browser and log in
- The page will display a
device authorization code — ask the user to copy and paste that code back
- Once the user provides the code, exchange it for a token:
PuzleReadingClient.exchange_device_code(code)
# Token is saved internally — never returned or exposed
- Confirm to the user: "Connected to Puzle successfully!"
- 3. After setup is complete, briefly explain what the skill can do:
- "You can now send me any URL, article, PDF, or text and I'll save it to your Puzle
reading library. I can also summarize articles, search across your readings, and more.
Just try sending me a link!"
Prerequisites
The bundled Client SDK lives at scripts/puzle_reading.py relative to this skill directory.
It requires the requests library and can be used as a Python library or a standalone CLI tool.
As a Python library:
CODEBLOCK2
As a CLI tool:
CODEBLOCK3
CLI Reference
The SDK doubles as a command-line tool. All data commands output JSON to stdout; errors go to stderr.
CODEBLOCK4
CLI Examples
CODEBLOCK5
Token Management
Token is stored in ~/.config/puzle/config.json with file permission 0o600 (owner read/write only).
The only way to obtain a token is through the device authorization code exchange flow.
Token confidentiality rules:
- - NEVER output, display, log, or echo the JWT token in any form
- NEVER accept a raw JWT token from the user — if a user pastes a JWT (starts with
eyJ...),
refuse it and guide them through the device auth flow instead
- - NEVER set the token as an environment variable
- The token is managed exclusively by the SDK internally (
_save_token / _load_token are private)
User needs to authorize (no token or token expired)
Guide them through device authorization:
- 1. Ask the user to open https://read-web.puzle.com.cn/device-auth and log in
- The page displays a device authorization code — ask the user to paste that code back
- Exchange the code for a token:
PuzleReadingClient.exchange_device_code(code)
# Token is saved internally — never returned or exposed
client = PuzleReadingClient() # now auto-loads the saved token
- 4. Confirm: "Connected to Puzle successfully!"
Note: The authorization code is short-lived and single-use — it is safe for users to paste
into the conversation. The actual JWT token never appears in chat history.
Token already configured (most common case)
Once a token has been saved, all subsequent calls work automatically — no need to ask the user again:
CODEBLOCK7
You can check beforehand:
CODEBLOCK8
Token expired (401 error)
The token is valid for approximately 7 days. When you receive PuzleAPIError(code=401):
- 1. Tell the user their token has expired
- Guide them through device authorization again (see "User needs to authorize" above)
- Exchange the new code: INLINECODE8
Two Modes of Use
Mode A: Save for later ("Read It Later")
When the user just wants to save content — no need to wait for processing. Create the reading,
give user the web link, done.
CODEBLOCK9
Use this mode when:
- - "save this" / "bookmark" / "read it later" / "store this link"
- "save all of these" — batch-saving multiple links
- User uploads a file but doesn't ask for any analysis — "just store this PDF"
- User shares a link in passing without asking a question about its content
- "keep it for later"
Mode B: Analyze now (background processing)
When the user wants to understand, summarize, or work with the content, processing takes
30–90 seconds. To avoid blocking the conversation:
- 1. Create the reading and immediately give user the web link
- Spawn a background task / subagent to run
wait_for_reading() + analysis - When the background task completes, present the result to the user
CODEBLOCK10
Why background processing? wait_for_reading() blocks for 30–90 seconds. Running it in
a subagent or background process keeps the conversation responsive. The user gets the web link
instantly and can start reading there, while the analysis runs in parallel.
Use this mode when:
- - "what does this article say" / "summarize this" / "give me a summary"
- "what are the key findings in this PDF" — need to read and analyze
- "compare the viewpoints of these two articles" — need content from multiple readings
- User asks a specific question about the article content
- You need to search across readings afterwards
When to Use Which Method
User shares a URL — Decision Chain
When the user gives you a link, do NOT call create_reading_from_url() directly.
Try the following approaches in order, falling through to the next step only on failure:
CODEBLOCK11
Step 1 readability extraction example:
CODEBLOCK12
When to skip directly to Step 3 (no need to attempt Step 1/2):
- - User explicitly says "let Puzle fetch it" or "use the URL method"
- Batch-saving multiple links (efficiency over quality — no need to fetch each one individually)
User provides content directly — create_reading_from_html()
| User says | Example |
|---|
| Pastes raw HTML or article text | "Analyze this content: ..." |
| Provides pre-fetched page content |
"I already scraped this page" |
| Content from JS-rendered page | "This page is JS-rendered, here's the source" |
User has a local file — create_reading_from_file()
| User says | Example |
|---|
| Uploads or references a local file | "Analyze this PDF" / "Read this report" |
| Shares meeting notes, memos, private docs |
"Here are today's meeting notes, save them" |
| Provides pure text to persist | "These are my notes" — save as temp
.md first |
| Shares images for analysis | "Look at this screenshot" (JPG/PNG) |
| Shares audio for transcription | "Process this recording" (MP3/WAV) |
|
Internal link content already retrieved | Content from Step 1, saved as file then uploaded |
search() — User wants to find something in their readings
| User says | Example |
|---|
| Recalls reading something before | "I read an article about microservices before" |
| Looking for a specific concept |
"Is there anything about attention mechanism in my library" |
| Cross-referencing topics | "Find everything mentioning transformer" |
| Fact-checking against saved readings | "I remember an article said X, find it for me" |
| Building on prior knowledge | "What did that paper say again" |
list_readings() — User wants to browse their collection
| User says | Example |
|---|
| Checking recent reading history | "What have I read recently" / "Show my reading history" |
| Looking for a specific reading by title |
"What was that article I saved a few days ago" |
| Organizing or reviewing collection | "List my readings" |
get_reading_detail() — Need content of a known reading
| User says | Example |
|---|
| Wants to re-read a specific article | "Show me the content of reading #42" |
| Following up on a previous reading |
"Show me the full text of that article" |
| Checking if processing is complete | (internal use after creating a reading) |
Core Concepts
| Concept | Description |
|---|
| Reading | A content record in Puzle — created from a URL, HTML, or file |
| resource_type |
"link" (URL/HTML source) or
"file" (uploaded file) |
|
Status flow |
fetching → parsing → ai_reading → done / fail |
|
Processing | Creating a reading returns immediately; use
wait_for_reading() only when you need the full content right away |
Available Methods
All create_reading_* methods return a ReadingResult with a web_url field
(e.g. https://read.puzle.com.cn/read/42) — always share this link with the user.
1. Create reading from URL
CODEBLOCK13
2. Create reading from HTML (pre-fetched content)
Use when the content has already been scraped — skips the fetching phase, so processing is faster.
CODEBLOCK14
3. Create reading from local file
Supported types: PDF, TXT, MD, CSV, JPG, PNG, WebP, GIF, MP3, WAV
CODEBLOCK15
The SDK automatically handles MD5 calculation and S3 upload internally.
If the user provides pure text, private docs, or other text material, you can save them as
a local file and use this method to create a reading.
4. Get reading detail
CODEBLOCK16
Use resource_type="file" for file-based readings. The SDK routes to the correct endpoint.
5. List readings
CODEBLOCK17
Returns a mixed list of link and file readings, sorted by most recent first.
6. Semantic search
CODEBLOCK18
Each result contains: reading_id, reading_title, resource_type, chunk_text, score.
Workflow Examples
Save a link for later (Mode A)
CODEBLOCK19
Batch save multiple links (Mode A)
CODEBLOCK20
Summarize an article (Mode B — background)
CODEBLOCK21
Upload and analyze a local PDF (Mode B — background)
CODEBLOCK22
Save user's text as a reading (Mode A)
When the user provides raw text (notes, memos, etc.), save it as a temp file first:
CODEBLOCK23
Search across all readings
CODEBLOCK24
Find and re-read a past article
CODEBLOCK25
Error Handling
API response format: The Puzle backend always returns HTTP 200. Errors are indicated by the
code field in the JSON body, with the error message in msg:
{"code": 401002, "msg": "Invalid or expired device code", "timestamp": 1775111299}
Success responses have
code: 200 and include a
data field.
The SDK raises PuzleAPIError with .code and .msg attributes for all API errors.
| Error | Meaning | What to do |
|---|
| INLINECODE39 | Invalid or expired device code | Ask user to get a new code from /device-auth |
| INLINECODE40 |
JWT token expired | Guide user through device auth again at https://read-web.puzle.com.cn/device-auth |
|
PuzleAPIError(code=403_003) | Tourist user limit exceeded | Ask user to upgrade their Puzle account |
|
PuzleAPIError(code=404_001) | Reading not found | Verify the reading_id is correct |
|
PuzleAPIError(code=500) during wait | Processing failed | Tell user the content couldn't be processed |
|
PuzleTimeoutError | Processing took >120s | Suggest trying again later; some large files take longer |
Constraints
- - Processing takes 30–90 seconds — only call
wait_for_reading() when you need the content (Mode B) - JWT tokens expire every 7 days — re-authorize via device auth when expired
- File upload link is valid for 1 hour
- INLINECODE46 max is 100 for list_readings
Data Privacy
Uploaded files (PDFs, images, audio, etc.) and web article content are sent to the Puzle service
for processing and storage. Before uploading on behalf of the user, inform them that:
- - Content will be transmitted to and stored by the Puzle service
- Users should not upload sensitive, confidential, or regulated data without verifying the
service's privacy and data retention policies
- - The service URL is hardcoded to
https://read-web.puzle.com.cn and cannot be overridden
Puzle 阅读技能
将网页文章、文档和文件保存到由 Puzle 驱动的个人阅读库中。
AI 会分析全文,并支持对你阅读过的所有内容进行语义搜索。
首次设置(在技能安装时显示)
当此技能首次激活时,立即问候用户并引导他们完成设置:
- 1. 检查是否已配置令牌:
python
from puzle_reading import PuzleReadingClient
if PuzleReadingClient.token
isconfigured():
# 令牌已存在,准备就绪
如果已配置,告诉用户:Puzle 阅读技能已就绪!你可以向我发送任何 URL、文件或文本,我会将其保存到你的阅读库中。你还可以搜索你读过的所有内容。
- 2. 如果未配置令牌,引导用户完成设备授权:
- 告诉用户:要开始使用,我需要连接到你的 Puzle 账户。
- 请用户在浏览器中打开
https://read-web.puzle.com.cn/device-auth 并登录
- 页面会显示一个
设备授权码——请用户复制并粘贴该代码回来
- 用户提供代码后,将其兑换为令牌:
python
PuzleReadingClient.exchange
devicecode(code)
# 令牌在内部保存——从不返回或暴露
- 向用户确认:已成功连接到 Puzle!
- 3. 设置完成后,简要说明该技能的功能:
- 你现在可以向我发送任何 URL、文章、PDF 或文本,我会将其保存到你的 Puzle 阅读库中。我还可以总结文章、搜索你的阅读内容等。试试给我发一个链接吧!
先决条件
捆绑的客户端 SDK 位于此技能目录相对路径下的 scripts/puzle_reading.py。
它需要 requests 库,可以作为 Python 库或独立 CLI 工具使用。
作为 Python 库:
python
import sys
sys.path.insert(0, <此技能目录>/scripts)
from puzle_reading import PuzleReadingClient
作为 CLI 工具:
bash
python3 <此技能目录>/scripts/puzle_reading.py <命令> [选项]
CLI 参考
SDK 也可作为命令行工具使用。所有数据命令输出 JSON 到 stdout;错误输出到 stderr。
puzle_reading <命令> [选项]
命令:
auth <代码> 将设备授权码兑换为令牌
(在 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 原始 URL(必需)
--title TITLE 文章标题(必需)
--content CONTENT 正文 HTML 字符串,或 @path 从文件读取(必需)
--text-content TEXT 纯文本版本,或 @path 从文件读取(必需)
--excerpt EXCERPT 简短摘要
--byline BYLINE 作者姓名
--site-name NAME 站点名称
--published-time TIME ISO 日期时间字符串
list 列出阅读库中的阅读项
--page N 页码(默认:1)
--page-size N 每页数量(默认:10,最大:100)
detail 获取阅读项的完整详情(type:link | file)
wait 等待处理完成,然后打印详情
--timeout SECS 最大等待秒数(默认:120)
--interval SECS 轮询间隔(默认:3)
search <查询> 在阅读项中进行语义搜索
--top-k N 最大结果数(默认:5)
--reading-ids IDS 逗号分隔的阅读项 ID,用于限定搜索范围
CLI 示例
bash
首次授权
./puzle_reading.py auth abc123-device-code
检查授权状态
./puzle_reading.py status
保存 URL
./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 令牌——如果用户粘贴了 JWT(以 eyJ... 开头),
拒绝它并引导用户通过设备授权流程
- - 绝不将令牌设置为环境变量
- 令牌完全由 SDK 内部管理(savetoken / loadtoken 是私有的)
用户需要授权(无令牌或令牌已过期)
引导他们完成设备授权:
- 1. 请用户在浏览器中打开 https://read-web.puzle.com.cn/device-auth 并登录
- 页面会显示一个设备授权码——请用户粘贴该代码回来
- 将代码兑换为令牌:
python
PuzleReadingClient.exchange
devicecode(code)
# 令牌在内部保存——从不返回或暴露
client = PuzleReadingClient() # 现在自动加载已保存的令牌
- 4. 确认:已成功连接到 Puzle!
注意: 授权码有效期短且一次性使用——用户可以安全地将其粘贴到对话中。
实际的 JWT 令牌永远不会出现在聊天历史中。
令牌已配置(最常见情况)
一旦令牌已保存,所有后续调用都会自动工作——无需再次询问用户:
python
client = PuzleReadingClient() # 自动从 ~/.config/puzle/config.json 读取
你可以事先检查:
python
if not PuzleReadingClient.tokenisconfigured():
# 引导用户完成设备授权
...
令牌已过期(401 错误)
令牌有效期约为 7 天。当你收到 PuzleAPIError(code=401) 时:
- 1. 告诉用户他们的令牌已过期
- 再次引导他们完成设备授权(参见上面的用户需要授权)
- 兑换新代码:PuzleReadingClient.exchangedevicecode(code)
两种使用模式
模式 A:稍后阅读(稍后阅读)
当用户只想保存内容时——无需等待处理。创建阅读项,给用户网页链接,完成。
python
result = client.createreadingfrom_url(https://example.com/article)
result.web_url → https://read.puzle.com.cn/read/42
告诉用户:已保存!你可以在此处查看:{result.web_url}
在以下情况下使用此模式:
- - 保存这个 / 书签 / 稍后阅读 / 存储这个链接
- 保存所有这些——批量保存多个链接
- 用户上传文件但不要求任何分析——只是存储这个 PDF
- 用户顺便分享链接而不询问其内容
- 留着以后看
模式 B:立即分析(后台处理)
当用户想要理解、总结或处理内容时,处理需要 30–90 秒。为避免阻塞对话:
- 1. 创建阅读项并立即给用户网页链接
- 生成后台任务/子代理来运行 waitforreading() + 分析
- 后台任务完成时,向用户呈现结果
python
步骤 1:创建并立即响应用户
result = client.create
readingfrom_url(https://example.com/article)
告诉用户:正在处理。你可以在此处预览:{result.web_url}
步骤 2:在后台