Install
openclaw skills install joplin-api-china通过 Joplin 官方本地 Data API(Clipper Server,端口 41184)查询和管理笔记、笔记本、标签。本技能仅对原始 Joplin REST API 做了 AI 调用引导和中文交互封装,不修改数据、不引入第三方服务,所有请求直连本机 Joplin 实例,安全可靠。触发词:"查 Joplin"、"Joplin 笔记"、"joplin search"、"create note in joplin" 等。
openclaw skills install joplin-api-chinaAccess Joplin data via the local HTTP REST API (clipper server).
Joplin data model:
Folders (笔记本/Notebooks) Notes (笔记)
├── 顶层笔记本 ├── 笔记属于某个 folder (parent_id → folder.id)
│ ├── 子笔记本 (parent_id→父ID) ├── 笔记内容是 Markdown 格式 (body 字段)
│ │ ├── 孙笔记本 └── 可附加标签(tags)、资源(resources)
│ │ │ └── ...无限层级
│ │ └── 孙笔记本
│ └── 子笔记本
└── 顶层笔记本
parent_id 形成无限层级的树形结构。每个 folder 可以有任意多个子 folder,没有层级深度限制。顶级 folder 的 parent_id 为空或指向一个不在当前列表中的父 ID。parent_id 归属到一个且仅一个 folder 下。笔记内容是 Markdown 格式(body 字段)。| 对象 | parent_id 含义 | 示例 |
|---|---|---|
| Folder | 指向父 folder ID;顶级 folder 为空/根 | parent_id: "abc123..." 表示该笔记本是 abc123... 的子目录 |
| Note | 指向所属 folder ID | parent_id: "folder_xyz" 表示该笔记在 folder_xyz 笔记本下 |
Token 存储在 OpenClaw 配置 skills.entries.joplin-api.env.JOPLIN_TOKEN。
每次操作 Joplin API 之前,必须按以下流程确认 token:
# 从 OpenClaw 配置读取 token
_jtoken() {
python3 -c "import json; d=json.load(open('$HOME/.openclaw/openclaw.json')); print(d['skills']['entries']['joplin-api']['env'].get('JOPLIN_TOKEN', ''))"
}
TOKEN=$(_jtoken)
然后判断:
| 情况 | 动作 |
|---|---|
TOKEN 有值(非空字符串) | → 继续调用 API,用此 token |
TOKEN 为空 | → 问用户要 token(见下方提示文案) |
当 token 为空时,告知用户以下内容:
Joplin API Token 尚未配置。请按以下步骤获取:
- 打开 Joplin 桌面端
- 进入 选项/偏好设置 → Web Clipper
- 找到 Access password(访问密码),这就是 token
- 把这段密码发给我,我会写入配置文件
同时请确认 Joplin Clipper Server 已启用(同一页面有开关)。
本技能直接调用 Joplin 官方本地 REST API,具备完整的 CRUD 能力。为保护数据安全:
# 将用户提供的 token 写入 OpenClaw 配置
_jset_token <TOKEN_VALUE> {
python3 -c "
import json, sys
path = '$HOME/.openclaw/openclaw.json'
d = json.load(open(path))
d.setdefault('skills', {}).setdefault('entries', {}).setdefault('joplin-api', {}).setdefault('env', {})
d['skills']['entries']['joplin-api']['env']['JOPLIN_TOKEN'] = sys.argv[1]
json.dump(d, open(path, 'w'), indent=2)
print('Token saved.')
" "$1"
}
写入完成后向用户确认 "Token 已保存,下次操作自动使用。"
重复 Step 0:先读配置确认 token → 有值则用,无值则再问。
Base URL: http://localhost:41184
Verify service:
curl -s http://localhost:41184/ping
# Expected: "JoplinClipperServer"
# 1. 确认 token
TOKEN=$(python3 -c "import json; d=json.load(open('$HOME/.openclaw/openclaw.json')); print(d['skills']['entries']['joplin-api']['env'].get('JOPLIN_TOKEN', ''))")
# 2. 检查是否有值
if [ -z "$TOKEN" ]; then
echo "TOKEN_MISSING"
fi
# 3. 有 token 则调用 API
curl -s "http://localhost:41184/notes?token=$TOKEN&limit=5" | jq .
The recommended workflow to find content about a topic:
# Search folders by title (case-insensitive, supports * wildcard)
curl -s "$JOPLIN_BASE/search?query=r730&type=folder&token=$JOPLIN_TOKEN" | jq .
# Get all notes in a specific folder
curl -s "$JOPLIN_BASE/folders/FOLDER_ID/notes?token=$JOPLIN_TOKEN&fields=id,title,updated_time&limit=50" | jq .
# Get note body (Markdown)
curl -s "$JOPLIN_BASE/notes/NOTE_ID?token=$JOPLIN_TOKEN&fields=id,title,body" | jq .
# Search notes by keyword (full-text, returns id/title only for speed)
curl -s "$JOPLIN_BASE/search?query=r730+购买&token=$JOPLIN_TOKEN&fields=id,title" | jq .
# Then read the matching note
curl -s "$JOPLIN_BASE/notes/NOTE_ID?token=$JOPLIN_TOKEN&fields=body" | jq .
Tip: When searching with
bodyfield, response can be large. Search withid,titlefirst, then fetchbodyfor the specific note you want.
# Returns flat list; use parent_id to reconstruct tree
curl -s "$JOPLIN_BASE/folders?token=$JOPLIN_TOKEN&fields=id,title,parent_id" | jq .
Each folder has:
id — folder IDtitle — folder nameparent_id — parent folder ID (empty/null = top-level)deleted_time — 0 = active, >0 = soft-deleted# Get all folders and build tree structure
curl -s "$JOPLIN_BASE/folders?token=$JOPLIN_TOKEN&fields=id,title,parent_id" | python3 -c "
import sys, json
folders = json.load(sys.stdin)
if isinstance(folders, dict):
folders = folders.get('items', folders)
# Build lookup
by_id = {f['id']: f for f in folders if f.get('deleted_time', 0) == 0}
# Attach children
for f in by_id.values():
pid = f.get('parent_id', '')
if pid and pid in by_id:
by_id[pid].setdefault('children', []).append(f)
# Print tree (top-level only parents)
def show(items, indent=0):
for item in sorted(items, key=lambda x: x['title']):
print(' ' * indent + f'[{item[\"id\"][:8]}] {item[\"title\"]}')
show(item.get('children', []), indent + 1)
roots = [f for f in by_id.values() if not f.get('parent_id') or f['parent_id'] not in by_id]
show(roots)
"
# Search for a notebook by title
curl -s "$JOPLIN_BASE/search?query=r730&type=folder&token=$JOPLIN_TOKEN&fields=id,title,parent_id" | jq .
curl -s "$JOPLIN_BASE/folders/FOLDER_ID/notes?token=$JOPLIN_TOKEN&limit=100&order_by=updated_time&order_dir=DESC&fields=id,title,updated_time" | jq .
# Title + body only
curl -s "$JOPLIN_BASE/notes/NOTE_ID?token=$JOPLIN_TOKEN&fields=id,title,body" | jq .
# With metadata
curl -s "$JOPLIN_BASE/notes/NOTE_ID?token=$JOPLIN_TOKEN&fields=id,title,body,parent_id,created_time,updated_time" | jq .
在执行前向用户展示将要创建的笔记标题、内容和目标笔记本,获得确认后再执行:
# 预览后执行
curl -s -X POST "$JOPLIN_BASE/notes?token=$JOPLIN_TOKEN" \
--data '{"title":"Title","body":"Markdown content","parent_id":"FOLDER_ID"}' | jq .
在执行前向用户展示将要修改的笔记标题、当前内容摘要和拟修改内容,获得确认后再执行:
# 预览后执行
curl -s -X PUT "$JOPLIN_BASE/notes/NOTE_ID?token=$JOPLIN_TOKEN" \
--data '{"title":"New Title"}' | jq .
默认行为:移至回收站(软删除),用户可恢复。
除非用户明确请求永久删除,否则不使用 permanent=1。
# 移至回收站(默认,可恢复)
curl -s -X DELETE "$JOPLIN_BASE/notes/NOTE_ID?token=$JOPLIN_TOKEN" | jq .
执行前必须:
# Tags on a note
curl -s "$JOPLIN_BASE/notes/NOTE_ID/tags?token=$JOPLIN_TOKEN" | jq .
# Resources (attachments) on a note
curl -s "$JOPLIN_BASE/notes/NOTE_ID/resources?token=$JOPLIN_TOKEN" | jq .
# List all tags
curl -s "$JOPLIN_BASE/tags?token=$JOPLIN_TOKEN&fields=id,title" | jq .
# Search tags (supports * wildcard)
curl -s "$JOPLIN_BASE/search?query=project-*&type=tag&token=$JOPLIN_TOKEN" | jq .
# Notes with a specific tag
curl -s "$JOPLIN_BASE/tags/TAG_ID/notes?token=$JOPLIN_TOKEN" | jq .
All list endpoints return { "items": [...], "has_more": true/false }.
# Page 2
curl -s "$JOPLIN_BASE/notes?token=$JOPLIN_TOKEN&limit=10&page=2" | jq .
Parameters: page (starts at 1), limit (max 100), order_by, order_dir (ASC/DESC).
0 or 1deleted_time: 0 = active, >0 = soft-deletedpermanent=1fields=id,title 提速,找到目标后再获取 bodyparent_id 构建树形关系See references/api-docs.md for the complete API reference (all endpoints, properties, error handling).