@reedef/r1
v0.7.5
Published
Personal backup & sync CLI — Obsidian vault ↔ S3-compatible storage via rclone bisync, with Resend email notifications and launchd scheduling on macOS.
Maintainers
Readme
r1
个人备份与同步 CLI 工具集。
安装
git clone <repo-url> ~/Project/r1
cd ~/Project/r1
npm link # 全局可用 r1依赖:Node 18+、rclone(brew install rclone)。
cloudcli 子命令
修复 CloudCLI Terminal 插件因 node-pty 原生模块与当前平台不匹配导致的 Disconnected / Connection lost。
r1 cloudcli fix-terminal命令会读取 CloudCLI 主包当前使用的 node-pty 版本,在 Terminal 插件目录精确安装同版本,执行插件构建,并验证 node-pty 能否加载。它会更新插件自己的 package.json / package-lock.json,但不会复制主包的 node_modules,也不会自动重启 CloudCLI。
默认路径:
- CloudCLI 主包:
$(npm root -g)/@cloudcli-ai/cloudcli - Terminal 插件:
~/.claude-code-ui/plugins/cloudcli-plugin-terminal
路径不同时可使用参数:
r1 cloudcli fix-terminal \
--cloudcli-dir /path/to/@cloudcli-ai/cloudcli \
--plugin-dir /path/to/cloudcli-plugin-terminal也兼容环境变量 CLOUDCLI_DIR、PLUGIN_DIR、GLOBAL_ROOT,命令行参数优先。在 Docker 场景中应进入容器后执行。修复成功后,请按 PM2、systemd、Docker Compose、手动进程或桌面应用等实际部署方式重启 CloudCLI。
self-update 子命令
默认行为:
- 运行任意普通命令前,
r1会按~/.config/r1/self-update.json中的intervalHours检查 npm 最新版 - 发现新版本时直接升级,不询问;升级成功后自动重跑原命令
- 升级
r1时会同步收敛内置保留仓库r1-skills([email protected]:Reedef/r1-skills.git) - 非黑名单 Skill 会自动安装到已存在的 Agent Skill 目录;黑名单 Skill 会被自动卸载
命令:
r1 self-update
r1 self-update config --interval-hours 24
r1 self-update blacklist add r1-dingdoc
r1 self-update blacklist remove r1-dingdoc
r1 self-update blacklist listobsidian 子命令
支持多个 Obsidian vault,每个 vault 与各自的 S3 兼容存储双向同步(rclone bisync),结果通过 Resend 发邮件。
命令一览
r1 obsidian list # 列出已配置的 vault
r1 obsidian sync <vault> # 跑一次指定 vault 的双向同步
r1 obsidian install <vault> | --all # 装 launchd 定时任务
r1 obsidian uninstall <vault> | --all # 卸载 launchd 定时任务
r1 obsidian status [vault] # 查 launchd / env / rclone remote 状态每个 vault 一个独立 launchd job(label 为 com.aliyz.obsidian-backup.<vault>),互不影响。
首次配置
1. 准备全局 env(只放 Resend API key)
跑一次 r1 obsidian install,env 文件不存在时会问你是否生成空模板(mode 600),y 即可,然后编辑:
$EDITOR ~/.config/r1/obsidian-backup.env填入:
RESEND_API_KEY=re_xxxxxxxxxxxxxxxxxxxxxxxx
# [email protected] # 可选 fallback,vault 没配 notifyEmail 时使用2. 写 vault 配置 ~/.config/r1/obsidian.json
{
"vaults": [
{
"name": "main",
"local": "~/Documents/Notes",
"remote": "qiniu-obsidian:yz-aio/obsidian-notes",
"schedule": [
{ "hour": 10, "minute": 45 },
{ "hour": 17, "minute": 45 }
],
"maxDelete": 10,
"notifyEmail": "[email protected]",
"localBackup": false
},
{
"name": "work",
"local": "~/Documents/WorkNotes",
"remote": "qiniu-obsidian:yz-aio/work-notes",
"schedule": [{ "hour": 22, "minute": 0 }],
"notifyEmail": "[email protected]",
"localBackup": true
}
]
}字段说明:
| 字段 | 必填 | 说明 |
|---|---|---|
| name | ✓ | vault 唯一标识,只能用 a-zA-Z0-9_- |
| local | ✓ | 本地 vault 路径,支持 ~/ |
| remote | ✓ | rclone remote 路径,格式 remote:bucket/dir |
| schedule | – | launchd 触发时间数组,默认 [10:45, 17:45] |
| maxDelete | – | 误删兜底,默认 10 |
| notifyEmail | – | 收件人;缺省时回退到 env 里的 NOTIFY_EMAIL |
| localBackup | – | 默认 false;为 true 时每周自动做一次本地 zip 备份(见下文) |
3. 确保 rclone 里有对应的 remote
rclone config # 添加 qiniu-obsidian 等 remote(S3/Qiniu/...)4. 装定时任务
r1 obsidian install --all # 或 r1 obsidian install <vault>5. 每个 vault 首次必须建 bisync 基线
r1 obsidian sync main --resync
r1 obsidian sync work --resync⚠️ 危险动作:bisync 第一次跑必须 --resync 建基线。--resync 默认用本地数据覆盖远端,运行时会先给出二次确认(非交互环境如 launchd 会直接中止,不会误建基线)。跑之前确认本地和远端内容一致(可以先 rclone check ~/Documents/Notes qiniu-obsidian:yz-aio/obsidian-notes)。
如果你想反过来用远端覆盖本地,不要用 --resync,改用普通同步并放宽误删兜底,例如 r1 obsidian sync main --max-delete=99。
日常使用
r1 obsidian sync main # 手动跑一次
r1 obsidian sync main --max-delete 50 # 临时放宽误删兜底
r1 obsidian sync main --dry-run # 只看不动
r1 obsidian sync main --skip-mail # 不发邮件
r1 obsidian status # 看所有 vault 状态
r1 obsidian status main # 单 vault立即触发某个 launchd job(不等定时):
launchctl kickstart gui/$(id -u)/com.aliyz.obsidian-backup.main加 / 删 vault
加:编辑 ~/.config/r1/obsidian.json 增项 → r1 obsidian install <new> → r1 obsidian sync <new> --resync。
删:先 r1 obsidian uninstall <name> → 从 obsidian.json 中删除该项。
本地周备份(localBackup)
vault 配置里设 "localBackup": true 后:
- 每次
r1 obsidian sync <vault>成功后,检查~/backup/obsidian/<vault>/下最近一个.zip的修改时间,距今 ≥ 7 天才会再做一次(首次没有 zip 则立刻做)。 - 备份文件名:
<vault>-YYYYMMDD-HHmmss.zip。 - 备份完成后发独立邮件通知(主题
📦 Obsidian [<vault>] 本周本地备份完成 @ <host>)。 - 备份失败只打印,不影响 sync 退出码;
--skip-mail只跳过邮件,备份仍会创建。 - 不做自动轮转,老 zip 自己清理;如果想手动触发下一次备份,删掉
~/backup/obsidian/<vault>/里最新那个 zip 即可。
默认行为(所有 vault 共用)
- 排除:
.DS_Store、*.log - 冲突策略:
--conflict-resolve newer,输者重命名为*.conflict-1 - 日志:
/tmp/obsidian-backup-<vault>.log+.err,rclone 详细 JSON 日志在~/obsidian_notes_rclone_backup-<vault>.log - 邮件发件人:
Obsidian Backup <[email protected]>(Resend 沙箱) - bisync 状态文件:
~/Library/Caches/rclone/bisync/(rclone 默认,按 path1/path2 自动区分不同 vault)
邮件主题示例
✅ Obsidian [main] 同步成功 @ Jacks-MacBook-Pro
❌ Obsidian [work] 同步失败 @ Jacks-MacBook-Promcp 子命令
直连任意 streamable-http 的 MCP 网关(例如钉钉文档),用 JSON-RPC over HTTP 调用其工具,不需要 MCP SDK,也不依赖 Claude / 任何 MCP 客户端。
命令一览
r1 mcp list # 列出已配置的 server
r1 mcp add <name> <url> [--type T] # 注册 / 更新一个 server(默认 type=streamable-http)
r1 mcp remove <name> # 删除一个 server
r1 mcp <server> # 列出该 server 的工具(等价于 <server> tools)
r1 mcp <server> tools # 同上
r1 mcp <server> call <tool> [json] # 调用工具,参数是一段 JSON 字符串用法示例(以钉钉文档为例)
# 1. 注册 server(url 里的 ?key= 就是鉴权,整串从钉钉 MCP 配置里拿)
r1 mcp add dingdoc "https://mcp-gw.dingtalk.com/server/xxxx?key=yyyy"
# 2. 看有哪些工具
r1 mcp dingdoc
# 3. 调用工具
r1 mcp dingdoc call list_nodes '{"pageSize":10}'
r1 mcp dingdoc call search_documents '{"keyword":"周报"}'
r1 mcp dingdoc call get_document_content '{"nodeId":"xxxx"}'配置 ~/.config/r1/mcp.json
r1 mcp add 会自动维护这个文件(权限锁 0600),也可手动编辑:
{
"servers": {
"dingdoc": {
"type": "streamable-http",
"url": "https://mcp-gw.dingtalk.com/server/xxxx?key=yyyy"
}
}
}| 字段 | 必填 | 说明 |
|---|---|---|
| <name> | ✓ | server 唯一标识,只能用 a-zA-Z0-9_-,且不能是 list/add/remove |
| url | ✓ | MCP 网关地址,http/https;鉴权一般就在 query 里(如 ?key=) |
| type | – | 默认 streamable-http(目前仅支持这一种) |
⚠️ 这个文件里含访问凭证(url 中的
key),不要提交到 git、不要分享。r1 mcp list输出会把key/token等自动打码。
行为说明
- 这类网关实测是无状态的:单次 POST 即调用,不做
initialize握手 / session 管理。 - 响应是
application/json或 SSE,两种都解析。工具结果若是 JSON 字符串会自动美化输出。 - 请求 30s 超时;HTTP 非 2xx、JSON-RPC error、网络失败都会给出可读报错(url 中的凭证已打码)。
skill 子命令(个人 GitHub Skills)
管理自己有权限读取或写入的 GitHub Skill 仓库。安装时直接把 Claude 的 ~/.claude/skills/<skill> 与 Codex 的 ~/.agents/skills/<skill> 软链接到 r1 管理的 Git 工作树,因此直接编辑 Agent 目录中的 Skill,等同于修改该工作树,可再发布回源仓库。
命令一览
r1 skill repo add <name> <github-url> [--ref main] [--path skills]
r1 skill repo list
r1 skill repo remove <name>
r1 skill available <repo>
r1 skill find <keyword> [--repo <name>]
r1 skill install <skill> | <repo>/<skill> [--agent claude|codex|all]
r1 skill list # 列出全部已安装 Skill,标记 r1 管理项
r1 skill status
r1 skill update <repo>/<skill> | --all [--dry-run]
r1 skill publish <repo>/<skill> | --all [-m "message"] [-y] [--dry-run]首次使用
r1 skill repo add infra [email protected]:Reedef/ai.infra.git
r1 skill find dingdoc
r1 skill available infra
r1 skill install r1-dingdoc --agent all仓库必须是 GitHub SSH URL(如 [email protected]:owner/repo.git)或 HTTPS URL。GitHub 的访问权限由本机 SSH key、Credential Manager 或 gh auth login 决定;r1 不保存 token。
r1 skill find 仅搜索 r1 skill repo add 配置的个人仓库,匹配 Skill 目录名及 SKILL.md 的 name、description;不会搜索公开市场。r1 skill install <skill> 在该范围内唯一命中时自动安装;同名结果有多个时会要求显式指定 <repo>/<skill>。r1 skill list 会扫描 Claude 与 Codex 的实际 Skill 目录,列出全部发现的 Skill,并标记哪些由 r1 管理。
Agent 检测规则:只要存在 ~/.claude,就视为已安装 Claude;只要存在 ~/.codex 或 ~/.agents,就视为已安装 Codex。安装 Skill 时若目标目录不存在,会自动创建 ~/.claude/skills 或 ~/.agents/skills。
更新与发布
# 仅把远端内容快进到本地工作树;有未发布修改时会拒绝覆盖
r1 skill update --all
# 在 ~/.claude/skills 或 ~/.agents/skills 中编辑过 Skill 后,检查状态并发布
r1 skill status
r1 skill publish infra/r1-dingdoc -m "docs: 更新钉钉文档 Skill"publish 会先显示 diff 和目标分支,确认后仅暂存指定 Skill,执行 commit、pull --rebase 和 push。没有写权限或遇到 rebase 冲突时会停止,保留本地修改与提交,绝不强推。非交互环境必须显式加 -y。
配置位于 ~/.config/r1/skills.json(权限 0600),本地 Git 工作树位于 ~/.cache/r1/skill-repos/。不要手动删除工作树;如需卸载或迁移,请先发布未提交的改动。
ding 子命令(钉钉文档 + 通讯录)
mcp 是通用网关,参数得手写 JSON;ding 是它上面的一层顺手封装,把动词直接映射成钉钉 MCP 工具调用,参数用普通 flag 传,不用手拼 JSON。分两组:
ding doc <动词>—— 钉钉在线文档(文字文档):读/最近/搜/建/改/删/加协作者 + 账号/文档库管理ding user search <关键词>—— 钉钉通讯录:搜成员拿 userId
兼容:旧写法
r1 dingdoc <...>仍等价于r1 ding doc <...>。
ding doc 多账号
一个「账号」= 一份钉钉文档 MCP 授权 URL(含 ?key=,绑定某个登录的钉钉账号)。凭证复用存在 ~/.config/r1/mcp.json,默认账号名单独记在 ~/.config/r1/dingdoc.json。命令默认走默认账号,加 --account <名> 临时切到别的账号。
r1 ding doc account add work "https://mcp-gw.dingtalk.com/server/xxx?key=yyy" # 加账号(首个自动设默认)
r1 ding doc account add home "https://mcp-gw.dingtalk.com/server/aaa?key=bbb"
r1 ding doc account list # 列出账号,标出默认(key 自动打码)
r1 ding doc account use home # 切默认账号
r1 ding doc account remove home # 删账号URL 从 https://mcp.dingtalk.com/#/detail?mcpId=9629 右侧「获取 MCP Server 配置」复制 StreamableHttp 那条。换钉钉账号就用那个账号登录后各复制一份,
account add成不同名字即可。兼容:早期直接
r1 mcp add dingdoc "<url>"的用户,没设默认账号时命令自动回退到名为dingdoc的账号,无需改动。
ding doc 文档库映射
repo 保存“易记名称 → workspaceId + 钉钉账号”的本地映射,并可指定一个默认文档库。添加时会先通过对应账号读取一次文档库,验证 ID 和权限;首个映射自动设为默认。
r1 ding doc repo add ai文档 <workspace-id> # 使用默认账号验证并保存
r1 ding doc repo add 团队知识库 <workspace-id> --account work # 绑定指定账号
r1 ding doc repo list # 列映射并标出默认
r1 ding doc repo use ai文档 # 切换默认文档库
r1 ding doc repo remove ai文档 # 仅删本地映射,不删远端库create 没有显式传 --folder 时,会优先写入默认文档库;若钉钉明确返回写入失败,会兜底写入该账号的个人文档库。没有设置默认文档库时直接写个人文档库。网络超时或连接中断不会自动回退,避免服务端其实已经创建成功而产生重复文档。显式 --folder 始终优先。
命令一览
r1 ding doc read <url-or-id> # 读正文,输出 Markdown(可 > out.md 落盘)
r1 ding doc info <url-or-id> # 元信息(标题/类型/创建者/时间)
r1 ding doc search [keyword] [--page-size N] # 搜索(省 keyword 返回最近访问)
r1 ding doc recent [--page-size N] # 最近访问/编辑的文档(每页最多 20 条)
r1 ding doc list [folder-id] [--page-size N] # 列目录子节点(省参数为「我的文档」根)
r1 ding doc create <title> [--folder <id>] [--body-file <f>] # 新建文档
r1 ding doc update <url-or-id> --body-file <f> [--mode append|overwrite] # 写正文
r1 ding doc rename <url-or-id> <new-name> # 重命名
r1 ding doc move <url-or-id> <folder-id> # 移动到目标目录
r1 ding doc delete <url-or-id> [-y] # 删除(需确认,-y 跳过)
r1 ding doc grant <url-or-id> <成员名...> [--role EDITOR|READER|DOWNLOADER|MANAGER] # 加协作者任意文档动作都能加 --account <名> 指定账号,例如 r1 ding doc list --account home。
用法示例
# 读一篇文档落盘(read 只吐正文,方便重定向)
r1 ding doc read https://alidocs.dingtalk.com/i/nodes/xxxx > 周报.md
# 搜索(默认账号)
r1 ding doc search 周报 --page-size 20
# 最近访问/编辑的文档(包含访问、更新时间和操作类型)
r1 ding doc recent --page-size 10
# 用另一个账号搜
r1 ding doc search 周报 --account home
# 新建带正文的文档(省略 --folder 时优先写默认文档库)
r1 ding doc create "四月周报" --body-file ./body.md
# 明确写入某个目录(覆盖默认文档库)
r1 ding doc create "四月周报" --folder <folder-id> --body-file ./body.md
# 往已有文档末尾追加(默认 append,安全)
r1 ding doc update <url-or-id> --body-file ./more.md
# 加协作者:直接写名字,内部自动转 userId(默认授「可编辑」)
r1 ding doc grant <url-or-id> 张三 李四
r1 ding doc grant <url-or-id> 张三 --role READER # 只读grant:加协作者(自动名字转 userId)
grant 把成员名字转成钉钉 userId 再授权,省得你手动查 id。默认授 EDITOR(可编辑)。
- 名字转 id 依赖一个通讯录 MCP(不是文档账号,就是
ding user用的那个,默认 server 名contacts),配置见下面「ding user」一节。 - 传纯数字会被当作 userId 直接使用,跳过查询。
- 一个名字在通讯录里匹配到多个人会报错(不会盲授),请用更精确的名字或直接传 userId。
- 只能授企业用户(
add_permission仅支持 USER 类型);群得去钉钉网页端加。
ding user(通讯录)
搜索组织成员、拿 userId(喂给 ding doc grant 或其它需要 userId 的地方)。目前只封装 search:
r1 ding user search 张三 # 返回匹配成员的 userId
r1 ding user search 张三 --contacts <名> # 指定通讯录 server(默认 contacts)通讯录是一个独立的 MCP(不是文档账号)。没授权时命令会提示去授权:
- 打开 https://aihub.dingtalk.com/#/detail?instanceId=4925735&detailType=instanceMcpDetail&mcpId=2400,完成授权后复制 StreamableHttp URL(含
?key=)。 r1 mcp add contacts "<上一步复制的 URL>"
查过的关键词会缓存到 ~/.config/r1/ding-user-cache.json(按通讯录 server 分桶),二次查询直接命中——包括 ding doc grant,它会复用你之前 ding user search(或上次 grant)查过的 userId,不再打通讯录。只缓存查到的结果;加 --refresh 跳过缓存强制重查并刷新。
只封装了 search;部门、按手机号查等其它通讯录能力直接用 r1 mcp contacts call <tool> '<json>'。
选项
| 选项 | 用于 | 说明 |
|---|---|---|
| --account <名> | 全部 | 本次用哪个账号;省略走默认账号,回退到名为 dingdoc 的账号(--server 是其别名)|
| --folder <id> | create | 显式目标目录,优先于默认文档库;省略则走默认文档库,再兜底个人文档库 |
| --body-file <f> | create / update | 要写入的正文文件(真实换行,别手拼 \n)|
| --mode <m> | update | append(默认,末尾追加,安全)/ overwrite(清空重写,会删掉全文含图片评论)|
| --role <r> | grant | EDITOR(默认)/ READER / DOWNLOADER / MANAGER |
| --contacts <名> | grant / user search | 通讯录 MCP server 名(默认 contacts)|
| --refresh | grant / user search | 查 userId 时跳过缓存强制重查 |
| --page-size <n> | search / recent / list | 每页条数;recent 最大 20 |
| -y, --yes | delete | 跳过二次确认 |
行为说明
nodeId处处可直接传文档链接 URL 或 32 位 dentryUuid,网关自动识别。- 文档库映射与默认值保存在
~/.config/r1/dingdoc.json;账号凭证仍只在mcp.json。 - 只覆盖钉钉文字文档;电子表格 / AI 表格不在范围。不确定类型先
r1 ding doc info <url-or-id>看extension(adoc即文字文档)。 delete非交互环境(管道 / cron)默认不删,需-y显式确认。update默认append而非overwrite——overwrite会清空全文,危险动作要显式带--mode overwrite。
文件位置一览
| 用途 | 路径 |
|---|---|
| 仓库代码 | ~/Project/r1/ |
| Vault 配置 | ~/.config/r1/obsidian.json |
| MCP server 配置(含 ding 文档账号 + contacts 通讯录凭证) | ~/.config/r1/mcp.json |
| Skill 仓库配置与安装记录 | ~/.config/r1/skills.json |
| Skill Git 工作树 | ~/.cache/r1/skill-repos/ |
| self-update 配置 | ~/.config/r1/self-update.json |
| self-update 状态缓存 | ~/.config/r1/update.json |
| ding doc 默认账号、文档库映射与默认文档库 | ~/.config/r1/dingdoc.json |
| ding user 查过的 userId 缓存 | ~/.config/r1/ding-user-cache.json |
| 全局 env(Resend API key) | ~/.config/r1/obsidian-backup.env |
| launchd plist(每 vault 一个) | ~/Library/LaunchAgents/com.aliyz.obsidian-backup.<vault>.plist |
| bisync 状态 | ~/Library/Caches/rclone/bisync/ |
| 本地周备份 zip(启用 localBackup 时) | ~/backup/obsidian/<vault>/<vault>-YYYYMMDD-HHmmss.zip |
| 单次跑的日志 | /tmp/obsidian-backup-<vault>.{log,err} |
| rclone JSON 日志 | ~/obsidian_notes_rclone_backup-<vault>.log |
在另一台 Mac 上启用
brew install rclonegit clone <repo-url> ~/Project/r1 && cd ~/Project/r1 && npm link- 拷过去
~/.config/rclone/rclone.conf(含 remote 凭证) - 拷过去 / 重建
~/.config/r1/obsidian.json和~/.config/r1/obsidian-backup.env r1 obsidian install --all- 每个 vault 跑一次
r1 obsidian sync <vault> --resync(bisync 状态不能跨机器搬运)
