Skill: jiangsier-xyz/sync-notes (version 1.0.1)

--- name: sync-notes description: Sync the local Obsidian vault with a Cloudflare R2 bucket using rclone (S3 + crypt). Triggered by `/sync-notes`. Defaults to bidirectional sync; understands natural-language flags for partial download/upload by glob. version: 1.0.1 metadata: {"openclaw":{"emoji":"☁️","requires":{"bins":["rclone","bash"],"env":["CLOUD_NOTES_PATH"]}}} --- # sync-notes Skill Sync the local notes directory (`$CLOUD_NOTES_PATH`) with a **Cloudflare R2** bucket through an end-to-end encrypted **rclone crypt** remote. The skill is a thin orchestrator over a single bash entry point at `scripts/sync.sh`; the LLM's job is to translate the user's natural-language intent into the right subcommand. ## Trigger Activate **only** when the user message starts with `/sync-notes` (with or without trailing arguments). Examples: | User message | Intent | Action | |---|---|---| | `/sync-notes` | full bidirectional sync | `sync.sh bisync` | | `/sync-notes setup` | run the configuration wizard | `sync.sh setup` | | `/sync-notes init` | establish the bisync baseline (first run only) | `sync.sh init` | | `/sync-notes status` | show config + last-sync info | `sync.sh status` | | `/sync-notes 下载 welcome` | download files matching `welcome` | `sync.sh download '**welcome**'` | | `/sync-notes upload daily/*.md` | upload glob | `sync.sh upload 'daily/*.md'` | | `/sync-notes 把 ideas 目录拉下来` | download a directory | `sync.sh download 'ideas/**'` | ## How to interpret natural language After stripping the `/sync-notes` prefix, classify the remainder: 1. **Empty** → run `bisync` (default bidirectional sync). 2. Contains a recognised keyword: - `setup`, `配置`, `wizard` → `setup` - `init`, `初始化`, `baseline` → `init` - `status`, `状态`, `信息` → `status` 3. Mentions **download** intent (`下载`, `拉`, `pull`, `download`, `get`, `取回`): - Extract the target token(s) and convert to a glob (see "Glob rules" below). - Run `sync.sh download '<glob>'`. 4. Mentions **upload** intent (`上传`, `推`, `push`, `upload`, `send`): - Same as above with `sync.sh upload '<glob>'`. 5. Otherwise: ask the user to clarify; do not guess. ### Glob rules Globs are passed verbatim to rclone as `--include` filters and apply to *plain* (decrypted) paths. - A bare keyword such as `welcome` becomes `**welcome**` (matches anywhere in path). - A directory name like `ideas` becomes `ideas/**`. - Paths already containing wildcards (`*`, `?`, `[`) are passed through unchanged. - Filenames without extension default to matching `<name>` and `<name>.md`; combine with `**` if unsure. When in doubt, run `sync.sh download '<glob>' --dry-run` first. ## Workflow contract 1. **First run ever:** the script refuses `bisync` until a baseline exists. Tell the user to run `/sync-notes init` after confirming both sides are in the desired starting state. 2. **Backups:** before any write-side operation (`bisync`, `download`, `init`), the local notes directory is mirrored to `backups/latest/` (single rolling copy, previous one is replaced). 3. **Conflicts:** bisync runs with conflict markers. After every run the script scans for `*.conflict*` files; if any are found the script exits non-zero and lists them. **Surface this list to the user verbatim and ask which side to keep**—do not auto-resolve. 4. **Logs:** every invocation appends to `logs/sync.log` and emits a per-run file `logs/<UTC-timestamp>.log` for the rclone output. ## Filesystem layout ``` skills/sync-notes/ ├── SKILL.md # this file ├── README.md # short user-facing notes (optional) ├── config/ │ ├── rclone.conf # active rclone config (created by setup, gitignored) │ ├── rclone.conf.example # template │ ├── .env # non-rclone settings (created by setup, gitignored) │ └── .env.example # template ├── scripts/ │ ├── sync.sh # main entry point (dispatcher) │ └── setup.sh # interactive configuration wizard ├── state/ │ └── bisync/ # rclone bisync workdir (listings + lockfiles) ├── logs/ # rolling logs ├── backups/ │ └── latest/ # most recent local snapshot (single copy) └── .gitignore ``` `scripts/sync.sh` derives every path from its own location (`$(dirname "$0")/..`); no absolute paths are hard-coded. ## Required environment | Variable | Purpose | Required | |---|---|---| | `CLOUD_NOTES_PATH` | absolute path of the local notes directory | ✅ | The script aborts early with a clear message if `CLOUD_NOTES_PATH` is unset, missing, or not a directory. ## Configuration files ### `config/rclone.conf` Native rclone format with **two remotes**: ```ini [r2-raw] type = s3 provider = Cloudflare [REDACTED_SECRET] secret_access_key = <SECRET_ACCESS_KEY> endpoint = https://<ACCOUNT_ID>.r2.cloudflarestorage.com acl = private [notes] type = crypt remote = r2-raw:<BUCKET>/<OPTIONAL_PREFIX> filename_encryption = standard directory_name_encryption = true [REDACTED_SECRET] password2 = <OBSCURED> ``` ### `config/.env` Non-rclone settings consumed by `sync.sh`: ```sh # Name of the crypt remote in rclone.conf RCLONE_REMOTE=notes # Number of local backup snapshots to retain BACKUP_KEEP=1 # Extra rclone flags (optional) RCLONE_EXTRA_FLAGS="--transfers=4 --checkers=8" ``` `.example` versions ship with the skill; the wizard fills in the real values. ## Subcommands implemented by `sync.sh` | Subcommand | Behaviour | |---|---| | `setup` | Run `setup.sh`; collects R2 creds + crypt password(s); writes `rclone.conf` and `.env` with `chmod 600`. Idempotent: confirms before overwriting. | | `init` | First-time baseline: `rclone bisync --resync`. Refuses to run if a baseline already exists unless `--force` is passed. | | `bisync` (default) | `rclone bisync $LOCAL notes:` with conflict reporting. Backs up first. | | `download <glob>` | `rclone copy notes: $LOCAL --include '<glob>'`. Backs up first. Defaults to `--dry-run` only when the user said "试" / "dry"; otherwise runs for real. | | `upload <glob>` | `rclone copy $LOCAL notes: --include '<glob>'`. No local backup needed; remote is authoritative. | | `status` | Prints config validity, last-sync timestamp, conflict files (if any), backup state. | ## Safety rules for the LLM - **Never** write secrets into chat. If the user pastes secrets, immediately move them into `config/rclone.conf` / `config/.env` and remove from any visible context. - **Never** invoke `rclone` with `--config` pointing somewhere outside this skill. - **Never** run `bisync` if `status` reports unresolved conflicts; ask first. - When `download`/`upload` glob match count is ambiguous, run with `--dry-run` first and show the list of matches. - Backups are single-copy; if you are about to do something risky (large delete, schema change), copy `backups/latest/` to `backups/manual-<UTC>/` manually before proceeding and tell the user. ## First-time onboarding (what to tell the user) ``` 1. /sync-notes setup # fill in R2 + crypt creds 2. export CLOUD_NOTES_PATH=... # point at the local notes dir 3. /sync-notes init # establish the bisync baseline 4. /sync-notes # routine bidirectional sync ```


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal:  | SkillSpector: suspicious (MEDIUM)