Joplin API(中文)
v1.0.0通过 Joplin 官方本地 Data API(命令行工具pper Server,端口 41184)查询和管理笔记、笔记本、标签。本技能仅对原始 Joplin REST API 做了 AI 调用引导和中文交互封装,不修改数据、不引入第三方服务,所有请求直连本机 Joplin 实例,安全可靠。触发词:"查 Joplin"、"Joplin 笔记"、"joplin 搜索"、"创建 note in joplin" 等。
运行时依赖
安装命令
点击复制技能文档
Joplin Data API 技能
访问 Joplin data via the local HTTP REST API (命令行工具pper server).
Architecture
Joplin data 模型:
Folders (笔记本/Notebooks) Notes (笔记) ├── 顶层笔记本 ├── 笔记属于某个 folder (parent_id → folder.id) │ ├── 子笔记本 (parent_id→父ID) ├── 笔记内容是 Markdown 格式 (body 字段) │ │ ├── 孙笔记本 └── 可附加标签(tags)、资源(resources) │ │ │ └── ...无限层级 │ │ └── 孙笔记本 │ └── 子笔记本 └── 顶层笔记本
Key Concepts Folders(笔记本) = 目录/文件夹,通过 parent_id 形成无限层级的树形结构。每个 folder 可以有任意多个子 folder,没有层级深度限制。顶级 folder 的 parent_id 为空或指向一个不在当前列表中的父 ID。 Notes(笔记) = 内容载体,每条笔记通过 parent_id 归属到一个且仅一个 folder 下。笔记内容是 Markdown 格式(body 字段)。 Tags(标签) = 跨笔记本的分类标记,可附加到任意笔记上,不受 folder 层级限制。 Resources(资源) = 附件文件(图片、文档等),可关联到笔记上。 parent_id 关系说明 对象 parent_id 含义 示例 Folder 指向父 folder ID;顶级 folder 为空/根 parent_id: "abc123..." 表示该笔记本是 abc123... 的子目录 Note 指向所属 folder ID parent_id: "folder_xyz" 表示该笔记在 folder_xyz 笔记本下 Configuration: 令牌 Lifecycle
令牌 存储在 OpenClaw 配置 技能s.entries.joplin-API.env.JOPLIN_令牌。 每次操作 Joplin API 之前,必须按以下流程确认 令牌:
Step 0: 确认 令牌(每次操作前必做) # 从 OpenClaw 配置读取 令牌 _j令牌() { python3 -c "导入 json; d=json.load(open('$HOME/.OpenClaw/OpenClaw.json')); print(d['技能s']['entries']['joplin-API']['env'].获取('JOPLIN_令牌', ''))" }
令牌=$(_j令牌)
然后判断:
情况 动作 令牌 有值(非空字符串) → 继续调用 API,用此 令牌 令牌 为空 → 问用户要 令牌(见下方提示文案) 令牌 缺失时的用户提示
当 令牌 为空时,告知用户以下内容:
Joplin API 令牌 尚未配置。请按以下步骤获取:
打开 Joplin 桌面端 进入 选项/偏好设置 → 网页 命令行工具pper 找到 访问 password(访问密码),这就是 令牌 把这段密码发给我,我会写入配置文件
同时请确认 Joplin 命令行工具pper Server 已启用(同一页面有开关)。
用户给出 令牌 后:写入配置 # 将用户提供的 令牌 写入 OpenClaw 配置 _j设置_令牌 <令牌_VALUE> { python3 -c " 导入 json, sys path = '$HOME/.OpenClaw/OpenClaw.json' d = json.load(open(path)) d.设置default('技能s', {}).设置default('entries', {}).设置default('joplin-API', {}).设置default('env', {}) d['技能s']['entries']['joplin-API']['env']['JOPLIN_令牌'] = sys.argv[1] json.dump(d, open(path, 'w'), indent=2) print('令牌 saved.') " "$1" }
写入完成后向用户确认 "令牌 已保存,下次操作自动使用。"
后续每次操作
重复 Step 0:先读配置确认 令牌 → 有值则用,无值则再问。
Base URL: http://localhost:41184
验证 服务:
curl -s http://localhost:41184/ping # Expected: "Joplin命令行工具pperServer"
完整操作示例(含 令牌 确认) # 1. 确认 令牌 令牌=$(python3 -c "导入 json; d=json.load(open('$HOME/.OpenClaw/OpenClaw.json')); print(d['技能s']['entries']['joplin-API']['env'].获取('JOPLIN_令牌', ''))")
# 2. 检查是否有值 if [ -z "$令牌" ]; then echo "令牌_MISSING" fi
# 3. 有 令牌 则调用 API curl -s "http://localhost:41184/notes?令牌=$令牌&limit=5" | jq .
工作流: 搜索 for a Topic
The recommended 工作流 to find content about a topic:
Step 1: Find relevant notebooks (folders) # 搜索 folders by title (case-insensitive, supports wildcard) curl -s "$JOPLIN_BASE/搜索?查询=r730&type=folder&令牌=$JOPLIN_令牌" | jq .
Step 2: 列出 notes inside a notebook # 获取 all notes in a specific folder curl -s "$JOPLIN_BASE/folders/FOLDER_ID/notes?令牌=$JOPLIN_令牌&fields=id,title,更新d_time&limit=50" | jq .
Step 3: Read note content # 获取 note body (Markdown) curl -s "$JOPLIN_BASE/notes/NOTE_ID?令牌=$JOPLIN_令牌&fields=id,title,body" | jq .
Alternative: Full-text 搜索 across all notes # 搜索 notes by keyword (full-text, returns id/title only for speed) curl -s "$JOPLIN_BASE/搜索?查询=r730+购买&令牌=$JOPLIN_令牌&fields=id,title" | jq .
# Then read the matching note curl -s "$JOPLIN_BASE/notes/NOTE_ID?令牌=$JOPLIN_令牌&fields=body" | jq .
Tip: When 搜索ing with body field, 响应 can be large. 搜索 with id,title first, then fetch body for the specific note you want.
Folder Hierarchy Operations 列出 all folders (flat 列出 with parent_id) # Returns flat 列出; use parent_id to reconstruct tree curl -s "$JOPLIN_BASE/folders?令牌=$JOPLIN_令牌&fields=id,title,parent_id" | jq .
Each folder has:
id — folder ID title — folder name parent_id — parent folder ID (empty/null = top-level) 删除d_time — 0 = active, >0 = soft-删除d Reconstruct folder tree in code # 获取 all folders and build tree structure curl -s "$JOPLIN_BASE/folders?令牌=$JOPLIN_令牌&fields=id,title,parent_id" | python3 -c " 导入 sys, json folders = json.load(sys.stdin) if isinstance(folders, dict): folders = folders.获取('items', folders)
# Build lookup by_id = {f['id']: f for f in folders if f.获取('删除d_time', 0) == 0}
# Attach children for f in by_id.values(): pid = f.获取('parent_id', '') if pid and pid in by_id: by_id[pid].设置default('children', []).应用end(f)
# Print tree (top-level only parents) def show(items, indent=0): for item in 排序ed(items, key=lambda x: x['title']): print(' ' indent + f'[{item[\"id\"][:8]}] {item[\"title\"]}') show(item.获取('children', []), indent + 1)
roots = [f for f in by_id.values() if not f.获取('parent_id') or f['parent_id'] not in by_id] show(roots) "
Find folder ID by name # 搜索 for a notebook by title curl -s "$JOPLIN_BASE/搜索?查询=r730&type=folder&令牌=$JOPLIN_令牌&fi