Skill: cfddd/obsidian-git-vault (version 0.0.2)

--- name: obsidian-git-vault description: >- 在 Obsidian 库（Markdown + Git）内检索笔记、创建与管理 .md；用 Git 查看历史与提交变更，并管理远程与同步（fetch/pull/push）。 当用户提及 Obsidian、vault、笔记检索、新建笔记、改笔记、移动重命名、Git、版本、提交、推送、拉取、远程、Git 初始化、配置 origin、SSH/HTTPS、token 权限、GitHub/GitLab 托管笔记库、vault 同步 SOP、定时同步时触发；需分步配置远程或权限说明时 Read git-remote-wizard.md；用户同意完整自动化同步工作流时 Read vault-sync-sop.md。 --- # Obsidian 库（Git）简易操作 以 **vault 根目录下的 Markdown 文件** 为真源；不依赖 Obsidian 应用 API。文件操作用 Glob、Grep、Read 与编辑工具；Git 操作用 `git -C "<vault>" ...`，且 **仅在该 vault 目录已是 Git 仓库时** 执行（用户要求 `git init` 时再初始化）。 ## 0. 变更后是否走同步 SOP - **用户已声明**采用「vault 同步 SOP」或同意安装后定时同步：每次对本 vault 的笔记 **创建/修改/移动/删除** 后，在同一轮对话中尽量按 [vault-sync-sop.md](vault-sync-sop.md) 执行第 1～3 步；定时任务（每 2h）仅在用户同意后在有 `openclaw` CLI 的环境尝试配置（详见该文件第 0 节）。 - **未声明**：日常仍可按第 5～6 节操作，**不**主动强推、不自动注册 cron。 - **能力边界**：Skill 不会在无用户同意时自行创建定时任务；`openclaw` 不可用则说明并停止。 ## 1. 解析 vault 根目录（每次先定根） 按优先级取 **第一个有效** 的目录（须为已存在的目录；解析路径时把 `~` 展开为用户主目录）： 1. 环境变量 `OBSIDIAN_VAULT`（可在终端执行 `echo "$OBSIDIAN_VAULT"` 判断）。 2. 用户在本对话中给出的 **vault 绝对路径**（仅本对话内沿用，不写入其它文件除非用户要求）。 3. 当前 Cursor **工作区根目录**：若根下存在 `.obsidian` 目录，可视为 vault 根。 4. **约定默认目录**：`~/.openclaw/workspace/obsidian-git-vault`（即 `$HOME/.openclaw/workspace/obsidian-git-vault`）在磁盘上已存在时作为 vault 根。 若以上皆无效：向用户索要 vault 绝对路径，或说明可 `mkdir -p ~/.openclaw/workspace/obsidian-git-vault` 后作为默认库再操作。禁止把上述列表以外的路径当作默认去猜。禁止操作落在所选 vault 根以外的未授权路径（防路径穿越）。 下文记 `V="<vault 绝对路径>"`，命令统一写 `git -C "$V"`。 ### 1.1 默认 `.gitignore` 不单独提供 `default.gitignore` 文件。新建库时运行 `bash "$SKILL_ROOT/scripts/init-default-vault.sh"`，若尚无 `$V/.gitignore` 会 **内联写入** 规则：系统杂项、`.env`/密钥、编辑器、可选用「不同步的」`.obsidian/workspace*.json`、`.trash/`、以及 **默认排除常见非文本附件**（图片/音视频/压缩包/Office/字体等）；若 vault 需把图片或 PDF 纳入 Git，删掉 `$V/.gitignore` 里对应段或改规则。 ### 1.2 Bash 脚本（`scripts/`） `SKILL_ROOT` 指含本 `SKILL.md` 的目录（openclaw 内多为 `.cursor/skills/obsidian-git-vault`）。优先用脚本代替手写长命令： - `bash "$SKILL_ROOT/scripts/resolve-vault.sh"`：打印 `$V`；可传覆盖路径为第一个参数；支持 `OBSIDIAN_VAULT`、`CURSOR_WORKSPACE`、当前目录含 `.obsidian`、`~/.openclaw/workspace/obsidian-git-vault`（须已存在）。 - `bash "$SKILL_ROOT/scripts/init-default-vault.sh"`：创建默认目录、`git init`、缺省时写入内置 `.gitignore`（含非文本附件忽略，见 §1.1）；可选参数为目标目录，默认 `~/.openclaw/workspace/obsidian-git-vault`。 - `bash "$SKILL_ROOT/scripts/vault-sync.sh"`：干净工作区下 `fetch`、判断领先/落后/分叉并 `push` / `pull`；见脚本内 `--help`。接受 SOP 强推时设 `OBSIDIAN_SOP_FORCE_LEASE=1`；`OBSIDIAN_PULL_REBASE=1` 则落后时 `pull --rebase`。 - `bash "$SKILL_ROOT/scripts/list-conflicts.sh"`：列出合并冲突文件。 - `bash "$SKILL_ROOT/scripts/openclaw-cron-vault-sync.sh"`：在本机有 `openclaw` 时注册约每 2 小时任务（需用户事前同意）；可用环境变量改 `OBSIDIAN_CRON_EXPR` 等。 **冲突合并**仍以人工/Agent 编辑 Markdown 为主；脚本不自动改冲突正文。 ## 2. 检索 - **按文件名**：在 vault 根下 `Glob`，如 `**/*.md` 或 `**/某目录/**/*.md`。 - **按正文 / 链接 / 标签**：在 vault 根下 `Grep`，可搜关键词、`[[`、`tags:`、`aliases:`、正文 `#tag` 等。 - **精读**：对命中列表再 `Read` 必要文件；回答中引用内容须来自已读文件，勿编造。 - **按 Git 历史**（用户要「谁改过 / 某文件历史」时）：`git -C "$V" log --oneline -n 20 -- path/笔记.md`，必要时 `git -C "$V" log -p -n 3 -- path/笔记.md`。 - `.obsidian` 下配置默认不当成「笔记正文」去搜，除非用户明确要求。 输出时列出 **相对 vault 的路径** 与简短命中说明即可。 ## 3. 管理 ### 3.1 修改 小步修改正文或 YAML frontmatter，保持 Markdown 与 frontmatter 语法有效。不大段重写用户未要求的内容。 ### 3.2 移动与重命名 用 `git -C "$V" mv 旧路径 新路径`（已纳入 Git 跟踪时优先）或普通 `mv` 后再 `git add`。完成后： - 对已知的 **旧文件名 / 旧路径**，在 vault 内 `Grep` 是否仍有 `[[旧名]]` 或旧相对链接。 - 向用户说明是否断链；若用户要求批量替换链接，再逐文件修改。**不**在未确认时大规模自动改链。 ### 3.3 删除 仅当用户 **明确要求删除** 某文件时执行；恢复未提交删除可用 `git -C "$V" restore --staged 路径` / `git restore 路径`（按当时状态）。 ## 4. 创建 - 路径与文件名由用户指定或按 vault 现有习惯（如 `inbox/`、`notes/`）；文件名避免非法字符，空格可保留或与用户约定用 `-`。 - 新建后若需纳入版本控制：`git -C "$V" add 路径`（用户要求提交时再与其它变更一起 `commit`）。 - 可选 YAML frontmatter，例如：`title`、`date`、`tags`；无模板时最小可用： ```yaml --- title: 笔记标题 date: YYYY-MM-DD tags: [] --- ``` - 若需在索引/MOC 中挂链，仅在用户指定目标文件时向其中追加 `[[新笔记名]]`。 ## 5. Git 版本控制（本地） 前提：`git -C "$V" rev-parse --is-inside-work-tree` 为真；否则仅在用户明确要求时 `git init`（初始化后提醒用户设置 `user.name` / `user.email` 若尚未配置）。 用户未要求时，不主动执行破坏性命令（见本节末）。 | 意图 | 典型命令（均在 `$V` 下） | |------|---------------------------| | 看工作区 | `git status` | | 看未暂存差异 | `git diff`；看已暂存 `git diff --staged` | | 暂存 | `git add 路径` 或 `git add -A`（用户确认范围后再用 `-A`） | | 提交 | `git commit -m "说明"`；说明应简短、可辨（如 `note: 添加 xxx`、`note: 修改 xxx`） | | 最近提交记录 | `git log --oneline -n 20` | | 某文件历史 | `git log --oneline -- path` | | 分支 | `git branch` / `git switch -c 新分支名`（用户要求时） | | 暂存手头改动 | `git stash push -m "说明"`；恢复 `git stash pop` 或 `git stash apply` | | 撤销未提交修改 | `git restore 路径`（丢弃工作区改动前需用户确认） | | 取消暂存 | `git restore --staged 路径` | **高风险**：`git reset --hard`、裸 `git push --force`、`git rebase` 等——默认仅用户 **逐字明确要求** 时执行。若用户 **已采用** [vault-sync-sop.md](vault-sync-sop.md)，则其中写明的「本地领先时推送 / `push --force-with-lease`」条件 **优先于** 本句的保守默认。 不要直接编辑 `.git` 目录内文件；不把 `.git` 当笔记检索。 ## 6. 远程仓库与同步 前提：网络与认证由用户环境解决（SSH key、credential、GitHub CLI 等）；推送/拉取需 **full_network** 类权限时由用户在环境中授权。 | 意图 | 典型命令 | |------|-----------| | 查看远程 | `git -C "$V" remote -v` | | 添加远程 | `git remote add origin <url>`（名称按用户习惯，不限于 `origin`） | | 修改远程 URL | `git remote set-url origin <url>` | | 删除远程 | `git remote remove 名称`（用户明确要求时） | | 取回远程分支（不合并） | `git fetch origin` 或 `git fetch --all` | | 与当前分支合并 | `git pull` 或 `git pull --rebase`（用户偏好 rebase 时再使用后者） | | 推送到远程 | `git push`；**首次**跟踪上游：`git push -u origin 当前分支名` | | 查看分支与跟踪关系 | `git branch -vv` | 合并冲突：默认提示冲突路径并手工清标记后 `git add`、`git commit`。若用户已采用 [vault-sync-sop.md](vault-sync-sop.md)，按其中 **第 3 节**（告警 + 按范围合并 Markdown）执行。 用户说「备份到 GitHub / 同步」时：先 `status`，再按需 `add`/`commit`，最后 `push`；若远程为空分支首次推送，可能需要 `-u`。 ### 6.1 配置向导（检测 Git、远程与权限） 从零检测 Git、初始化、添加/修改远程、选择 SSH 或 HTTPS、**权限收敛**（最小 scope、不把密钥写进仓库），或说明 **openclaw 仓库拉取 skill** 与 **笔记 vault 单独 `git pull`** 的区别时：**Read** [git-remote-wizard.md](git-remote-wizard.md)：先按其中 **「使用前：向用户提示的最小配置」** 说明仅需 `user.name` / `email` 与 remote，其余默认；再按该文件分步与用户确认。远程已配置且只做日常同步时可不读。 ## 7. 能力边界（本 skill 不含） 批量重构全库链接、Canvas（`.canvas`）自动改写、Dataview 查询执行、Obsidian 插件设置修改——有需求时再单独约定。已采用 `vault-sync-sop.md` 时合并策略以该文件为准，不与此条冲突。

path: vault-sync-sop.md
content: # 笔记库同步 SOP（工作流）

以下在 **用户明确同意采用本 SOP** 后执行；Agent 每次用本 skill **改动了 vault 内文件**（创建/修改/移动/删除）后，应在本轮对话末尾尽量走一遍 **第 1～3 步**（第 0 步的定时任务仅在意愿书与一次性配置时）。

---

## 0. 安装后告知与定时任务（openclaw）

**限制**：Skill 无法自行「安装」到系统；由 Agent **告知用户** 能力边界，并在用户 **书面同意** 后尝试用终端执行 openclaw CLI。若环境无 `openclaw` 或命令失败，说明原因并 **停止**，不反复重试。

1. 向用户说明：可配置 **每 2 小时** 触发一次「vault 同步检查」类任务；任务失败需用户查看 `openclaw cron runs <jobId>`（或等价），**本 SOP 不代替** openclaw 的失败自动熔断（按需由用户关闭/暂停任务）。

2. 用户同意后，在 **已配置好 openclaw 的环境** 中执行 skill 内脚本（可调参数，等价于下方手写命令）：

```bash
bash "<SKILL_ROOT>/scripts/openclaw-cron-vault-sync.sh"
```

或手写：

```bash
openclaw cron add \
  --name "obsidian-vault-sync" \
  --cron "0 */2 * * *" \
  --tz "Asia/Shanghai" \
  --session isolated \
  --message "按 obsidian-git-vault skill 的 vault-sync-sop.md 执行第 1～3 步同步检查；失败则向用户报告并停止，不要强推除非 SOP 条件满足且无错误。"
```

若 `openclaw cron add` 参数与现场 CLI 不同，以 `openclaw cron --help` 为准，保留 **2 小时间隔** 与 **明确引用本 SOP** 即可。

3. **失败则停止**：CLI 报错、无权限、无网络时，在一次尝试后停止并输出原文；不猜测成功。

---

## 1. 当前笔记 / 库是否存在

- 用主 [SKILL.md](SKILL.md) 第 1 节解析 `$V`。
- **若目标笔记路径在本地已存在**：进入第 2 步。
- **若不存在**：走 **默认库初始化**（用户未另指定时）：
  - `mkdir -p ~/.openclaw/workspace/obsidian-git-vault`（若缺则建）；
  - 将 `$V` 定在该目录；
  - `git rev-parse` 非仓库则 **`git init`**（用户同意时）；
  - 若无 `$V/.gitignore`，运行 `bash "<SKILL_ROOT>/scripts/init-default-vault.sh" "$V"` 或沿用其内置规则写入（见 SKILL §1.1）；
  - 再创建/写入用户要的笔记文件，按需 `git add` / `commit`。

---

## 2. 远程同步状态

前提：`git -C "$V" rev-parse --is-inside-work-tree` 为真。

**脚本优先**（工作区须无未提交变更；接受 SOP 强推时导出 `OBSIDIAN_SOP_FORCE_LEASE=1`）：

```bash
V="$(bash "<SKILL_ROOT>/scripts/resolve-vault.sh")"
export OBSIDIAN_VAULT="$V"
OBSIDIAN_SOP_FORCE_LEASE=1 bash "<SKILL_ROOT>/scripts/vault-sync.sh" "$V"
```

退出码：`vault-sync.sh` 中 `0` 已同步或完成 push/pull；`2` 分叉；`3` 无远程；`4` 无上游；`5` 有未提交文件；`10` pull 后仍存在冲突；其它见脚本 `--help`。

手写排查时：

1. `git -C "$V" remote -v`：**无远程** → **Read** [git-remote-wizard.md](git-remote-wizard.md)，指导配置后再回到本节。

2. `git -C "$V" fetch origin`（或当前上游远程名）；再看 `git -C "$V" status -sb` 与 `git -C "$V" log --oneline HEAD..@{u}`、`@{u}..HEAD`（无上游时先用 `git branch -vv` 确认是否需 `push -u`）。

3. 判定（同一分支、已设 upstream）：

| 情形 | 动作 |
|------|------|
| 与远程无提交差、工作区干净（或已 commit） | **停止** 同步操作（无需 push/pull）。 |
| **仅本地领先**（有本地提交未推送） | 在用户已同意本 SOP 前提下 **`git push`**；若远程因历史分歧拒绝，再按用户策略使用 **`git push --force-with-lease`**（优先于裸 `--force`）。 |
| **仅远程领先** | **`git pull`**（或用户偏好的 `git pull --rebase`）；随后处理冲突见第 3 步。 |
| **双方各有提交**（分叉） | 先向用户 **告警**；再拉取合并或 rebase；仍冲突则走第 3 步。 |

**说明**：与主 SKILL 中「强推需逐字确认」相比，**本文件优先级更高**——但仅当用户 **已声明采用本 SOP**。未采用时仍按 SKILL 保守规则。

---

## 3. 冲突处理（Obsidian 场景）

- **预设**：单文件同时编辑概率低，但仍 **先向用户告警**（列冲突路径与涉及提交）。
- **操作**：
  1. `bash "<SKILL_ROOT>/scripts/list-conflicts.sh"` 或 `git -C "$V" diff --name-only --diff-filter=U`，并阅读冲突文件中的 `<<<<<<<` 标记；
  2. **Read** 冲突全文，在笔记语义上 **合并为一版 Markdown**（可保留双方段落，用二级标题或 `---` 分隔），删掉冲突标记；
  3. `git add` 冲突文件，`git commit`（使用清晰说明如 `note: merge remote 与本地`）。
- 若冲突范围过大或涉及二进制/非笔记文件：**停止** 自动合并，请用户手工处理。

---

## Agent 自检

- [ ] 用户已同意采用本 SOP（含强推策略知情），再执行强推类命令。
- [ ] `$V` 为笔记库而非 openclaw 源码仓（除非用户明确要求操作后者）。
- [ ] 定时任务仅为「尝试配置」，失败不无限重试。


path: git-remote-wizard.md
content: # Git 与远程仓库配置向导（交互）

本文件供 Agent 在 **需要从零配置 Git、挂远程、或说明权限边界** 时 **Read**；日常检索/改笔记不必读。与 vault 根 `$V` 相关的命令一律写 `git -C "$V"`。

---

## 与 openclaw 仓库的关系（更新本 skill）

本 skill 文件位于 **openclaw 仓库**（例如 `.cursor/skills/obsidian-git-vault/`）时：

- 你在 **另一台机器或远端环境** 使用 openclaw：要先在 openclaw 目录执行 **`git pull`**（或等价同步），才能拿到最新的 `SKILL.md` / 本向导。
- **笔记 vault** 与 **openclaw 代码仓** 是两个不同的 Git 仓库，不要混用路径；配置远程时始终明确当前终端所在目录是 `$V` 还是 openclaw 根。

向用户说明时一句话即可：**「skill 随 openclaw 更新；笔记库单独 `git pull`。」**

---

## 向导触发条件

在以下情况走本向导（按步执行、逐步向用户确认结果）：

- 用户要「把笔记推到 GitHub / 配远程 / 克隆下来的库怎么同步」；
- `git -C "$V" rev-parse --is-inside-work-tree` 失败，或 `remote -v` 为空且用户要云端备份；
- 用户问 HTTPS 还是 SSH、token 权限、会不会太危险。

---

## 使用前：向用户提示的最小配置

进入本向导、**尚未开始执行具体 Git 命令前**，先用几句话告知用户：笔记库要正常工作，**需要用户明确提供或确认的只有下面两项**；**其它一律用本 skill 默认**（见主 [SKILL.md](SKILL.md)：vault 路径解析顺序、约定默认目录、`init-default-vault.sh` 写入的内置 `.gitignore`、[scripts/vault-sync.sh](scripts/vault-sync.sh) 的远程名默认 `origin` 等）。

**必须由用户配置（或确认已配置）的：**

1. **`user.name` 与 `user.email`**（提交身份）：可在 vault 根执行  
   `git -C "$V" config user.name "…"`、`git -C "$V" config user.email "…"`，或使用 `git config --global …`；以用户选择为准。
2. **远程仓库**：托管平台上的仓库地址（SSH 或 HTTPS），以及本地 remote 名称；**未特别说明时 remote 名称为 `origin`**，执行 `git remote add origin <url>` 即可。

**在用户确认「其余用默认」时 Agent 的行为：**

- 不额外追问分支命名规范、合并策略偏好、是否 rebase 等，除非执行时报错需要用户决定。
- vault 路径、`gitignore` 模板、同步脚本、openclaw 定时表达式等，均按 SKILL 与 [vault-sync-sop.md](vault-sync-sop.md) 已有默认，**不在向导里逐项展开**。

---

## 第 0 步：确认操作对象

1. 用前文规则解析 **vault 根** `$V`（`OBSIDIAN_VAULT` / 用户给定 / 工作区含 `.obsidian` / 已存在的 `~/.openclaw/workspace/obsidian-git-vault`）。
2. 向用户确认：**「以下 Git 命令均在笔记库目录执行：`<$V>`」**；若用户实际想操作的是 openclaw 仓库，改用 openclaw 根路径，并说明与 vault 不同。

---

## 第 1 步：检测是否为 Git 仓库

在 `$V` 执行：

```bash
git -C "$V" rev-parse --is-inside-work-tree 2>/dev/null && echo OK || echo NO_REPO
```

| 结果 | 动作 |
|------|------|
| `OK` | 进入第 2 步（身份与状态）。 |
| `NO_REPO` | 询问用户是否 **`git init`**；仅当用户同意时执行 `git -C "$V" init`，然后进入第 2 步。 |

**禁止**在未确认 `$V` 时初始化，避免在错误目录创建 `.git`。

---

## 第 2 步：身份与干净状态

1. 检查提交身份（无则提醒用户自行设置，不替用户编造）：

```bash
git -C "$V" config user.name
git -C "$V" config user.email
```

若为空：提示用户在 `$V` 或全局设置：

```bash
git -C "$V" config user.name "显示名"
git -C "$V" config user.email "邮箱"
```

2. 看工作区：`git -C "$V" status -sb`。若有未提交修改且用户即将改远程 URL 或首次 push，提醒先 **commit 或 stash**，避免与后续操作混淆。

---

## 第 3 步：检测远程

```bash
git -C "$V" remote -v
```

| 情况 | 动作 |
|------|------|
| 已有 `origin`（或其它名）且 URL 正确 | 进入第 5 步（权限与协议）；用户若仅要同步，可直接 `fetch`/`pull`/`push`（第 6 步）。 |
| 无远程，用户要关联 GitHub/GitLab | 进入第 4 步。 |
| URL 错误需更换 | 用户确认后 `git -C "$V" remote set-url <名称> <新 URL>`。 |

---

## 第 4 步：添加远程仓库

1. 请用户提供 **HTTPS 或 SSH** 克隆地址（示例：`[REDACTED_SECRET]:user/repo.git` 或 `https://github.com/user/repo.git`）。
2. 添加（名称默认 `origin`，若已占用则用用户指定名）：

```bash
git -C "$V" remote add origin <url>
```

已存在同名远程时：**不要**盲目 `add`；改用 `set-url` 或先 `remote remove`（需用户确认）。

3. 验证：`git -C "$V" remote -v`。

---

## 第 5 步：协议选择与权限收敛

### 5.1 SSH（推荐长期自用）

- **凭据形态**：本机 `~/.ssh/id_ed25519`（或 rsa）公钥登记到 GitHub/GitLab。
- **权限收敛**：密钥 **只加在个人账户**，仓库用 **私有** 若笔记敏感；Deploy Key 仅绑定单一仓库、只读或读写按需要选。
- **Agent 说明要点**：私钥不出库、不贴进聊天；远程 URL 用 `git@host:...`，不要在笔记仓提交任何密钥文件。

### 5.2 HTTPS + PAT（Personal Access Token）

- **权限收敛（GitHub 示例）**：勾选 **repo**（私有库读写）即可；**不要**勾选 `delete_repo`、workflow、整账户 admin，除非用户明确需要。
- **GitLab**：`write_repository` / `read_repository` 按私有与只读需求选最小集。
- **存储**：用系统钥匙串 / `git credential` / `gh auth`，**不要**把 token 写入仓库内 `.env` 或笔记；若必须落盘，路径加入 `.gitignore` 且永不提交。

### 5.3 与 Cursor / Agent 执行环境

- 终端执行 `git push`/`pull` 可能需 **网络权限**；认证失败时提示用户在本机终端或已登录的 `gh`/`glab` 重试，而非在对话里索要密码全文。

---

## 第 6 步：首次跟踪与同步

1. 取远程引用：`git -C "$V" fetch origin`（或用户指定的远程名）。
2. 若本地无提交、远程有默认分支：按用户意图 **`git pull origin main`**（或 `master`，以远程为准）或先建立初始提交再拉取；**有冲突**时列出冲突文件，指导解决标记后 `add` + `commit`。
3. 首次推送当前分支并设上游：

```bash
git -C "$V" push -u origin HEAD
```

分支名以 `git branch --show-current` 为准。

4. 若远程为空仓库：用户先本地 `commit` 再 `push -u`；必要时说明 **空仓库无默认分支** 的提示属正常。

---

## 第 7 步：收尾话术（给用户）

- **更新 skill**：openclaw 侧改动后需 **`git pull`** openclaw 仓库；与 vault 的 `pull` 分开操作。
- **日常笔记同步**：`status` → `add`/`commit` → `push`；拉取协作前先 `pull`。
- **权限**：能 SSH 就不用宽 scope PAT；PAT 能私有 `repo` 就不开整站权限。

---

## Agent 自检清单（执行前扫一眼）

- [ ] 当前 `$V` 是用户确认的笔记库根，不是 openclaw 根（除非用户明确要求操作 openclaw）。
- [ ] 未在仓库中创建含 token/私钥的文件。
- [ ] `init` / `remote add` / `push --force` 均有用户明确同意。
- [ ] 失败时给出 **认证失败 / 无权限 / 冲突** 三类之一的原因方向，不重复索要敏感信息全文。


path: scripts/openclaw-cron-vault-sync.sh
content: #!/usr/bin/env bash
set -euo pipefail
TZ="${OBSIDIAN_CRON_TZ:-Asia/Shanghai}"
NAME="${OBSIDIAN_CRON_NAME:-obsidian-vault-sync}"
EXPR="${OBSIDIAN_CRON_EXPR:-0 */2 * * *}"
MSG="${OBSIDIAN_CRON_MESSAGE:-按 obsidian-git-vault skill：对默认/当前 vault 执行 scripts/vault-sync.sh（先 resolve）；失败则报告并停止；强推仅当已接受 SOP 且 OBSIDIAN_SOP_FORCE_LEASE=1。}"

if ! command -v openclaw >/dev/null 2>&1; then
	echo "未找到 openclaw 命令，跳过 cron 注册。" >&2
	exit 1
fi

openclaw cron add \
	--name "$NAME" \
	--cron "$EXPR" \
	--tz "$TZ" \
	--session isolated \
	--message "$MSG"


path: scripts/init-default-vault.sh
content: #!/usr/bin/env bash
set -euo pipefail
# shellcheck disable=SC1091
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"

write_default_gitignore_if_missing() {
	local dest="$1"
	[[ -f "$dest/.gitignore" ]] && return 0
	cat >"$dest/.gitignore" <<'EOF'
# 系统杂项
.DS_Store
Thumbs.db
desktop.ini

# 本地密钥与环境变量（勿提交）
.env
.env.*
*.pem
*.key
id_rsa
id_ed25519

# 编辑器
.idea/
.vscode/
*.swp
*.swo
*~

# Obsidian：窗口布局易冲突；要整库同步 .obsidian 可删下两行
.obsidian/workspace.json
.obsidian/workspace-mobile.json

# 废纸篓目录（若启用）
.trash/

# 非文本附件（默认不入库；要提交图片/PDF 等可删除本段）
*.png
*.jpg
*.jpeg
*.gif
*.webp
*.bmp
*.ico
*.pdf
*.mp4
*.mov
*.mkv
*.zip
*.7z
*.rar
*.gz
*.tar
*.dmg
*.woff
*.woff2
*.ttf
*.otf
*.eot
*.doc
*.docx
*.xls
*.xlsx
*.ppt
*.pptx
EOF
}

V="${1:-${HOME}/.openclaw/workspace/obsidian-git-vault}"
mkdir -p "$V"
if ! require_git_repo "$V"; then
	git -C "$V" init
fi
write_default_gitignore_if_missing "$V"
printf '%s\n' "$(cd "$V" && pwd)"


path: scripts/vault-sync.sh
content: #!/usr/bin/env bash
set -euo pipefail
# shellcheck disable=SC1091
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
REMOTE="${OBSIDIAN_GIT_REMOTE:-origin}"
ALLOW_FORCE_LEASE="${OBSIDIAN_SOP_FORCE_LEASE:-0}"
REBASE_PULL="${OBSIDIAN_PULL_REBASE:-0}"

usage() {
	echo "用法: $0 [VAULT_DIR]" >&2
	echo "环境: OBSIDIAN_VAULT | CURSOR_WORKSPACE | 当前目录 .obsidian | 默认 ~/.openclaw/workspace/obsidian-git-vault" >&2
	echo "      OBSIDIAN_GIT_REMOTE (默认 origin)" >&2
	echo "      OBSIDIAN_SOP_FORCE_LEASE=1  push 被拒时尝试 --force-with-lease" >&2
	echo "      OBSIDIAN_PULL_REBASE=1  落后时使用 pull --rebase" >&2
	echo "退出码: 0 无需同步或已 push/pull 成功 | 1 参数错误 | 2 分叉 | 3 无远程 | 4 无上游 | 5 工作区未提交 | 其它 git 失败" >&2
}

if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then usage; exit 0; fi

V="$(resolve_vault "${1:-}")" || { usage; exit 1; }
V="$(cd "$V" && pwd)"

if ! require_git_repo "$V"; then
	echo "不是 Git 仓库: $V" >&2
	exit 1
fi

if [[ -n "$(git -C "$V" status --porcelain 2>/dev/null)" ]]; then
	echo "工作区有未提交变更，请先 commit 或 stash。文件:" >&2
	git -C "$V" status -s >&2
	exit 5
fi

if ! git -C "$V" remote get-url "$REMOTE" >/dev/null 2>&1; then
	echo "NO_REMOTE: 无远程 $REMOTE，参见 git-remote-wizard.md" >&2
	exit 3
fi

git -C "$V" fetch "$REMOTE"

if ! git -C "$V" rev-parse --verify "@{u}" >/dev/null 2>&1; then
	echo "NO_UPSTREAM: 当前分支未跟踪远程，例如: git push -u $REMOTE \"\$(git branch --show-current)\"" >&2
	exit 4
fi

read -r ahead behind _ < <(git -C "$V" rev-list --left-right --count HEAD..."@{u}" 2>/dev/null || echo "0 0")

if [[ "$ahead" -gt 0 && "$behind" -gt 0 ]]; then
	echo "DIVERGED: 本地领先 $ahead、落后 $behind，需人工合并或 rebase（见 vault-sync-sop 第 3 节）" >&2
	git -C "$V" log --oneline -n 5 HEAD >&2
	git -C "$V" log --oneline -n 5 "@{u}" >&2
	exit 2
fi

if [[ "$ahead" -eq 0 && "$behind" -eq 0 ]]; then
	echo "SYNC_OK: 与 @{u} 一致，无需 push/pull"
	exit 0
fi

if [[ "$ahead" -gt 0 && "$behind" -eq 0 ]]; then
	if git -C "$V" push; then
		echo "PUSH_OK"
		exit 0
	fi
	if [[ "$ALLOW_FORCE_LEASE" == "1" ]]; then
		echo "push 失败，尝试 --force-with-lease（需已接受 SOP）" >&2
		git -C "$V" push --force-with-lease
		echo "PUSH_FORCE_LEASE_OK"
		exit 0
	fi
	echo "push 失败；可设 OBSIDIAN_SOP_FORCE_LEASE=1 再试（force-with-lease）" >&2
	exit 9
fi

if [[ "$ahead" -eq 0 && "$behind" -gt 0 ]]; then
	if [[ "$REBASE_PULL" == "1" ]]; then
		git -C "$V" pull --rebase
	else
		git -C "$V" pull
	fi
	if [[ -n "$(git -C "$V" diff --name-only --diff-filter=U)" ]]; then
		echo "CONFLICT: 存在合并冲突" >&2
		git -C "$V" diff --name-only --diff-filter=U >&2
		exit 10
	fi
	echo "PULL_OK"
	exit 0
fi

exit 1


path: scripts/resolve-vault.sh
content: #!/usr/bin/env bash
set -euo pipefail
# shellcheck disable=SC1091
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
if V="$(resolve_vault "${1:-}")"; then
	printf '%s\n' "$V"
else
	echo "无法解析 vault：请设置 OBSIDIAN_VAULT、传入绝对路径、在含 .obsidian 的目录执行，或先 init-default-vault.sh。" >&2
	exit 1
fi


path: scripts/list-conflicts.sh
content: #!/usr/bin/env bash
set -euo pipefail
# shellcheck disable=SC1091
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
V="$(resolve_vault "${1:-}")" || exit 1
V="$(cd "$V" && pwd)"
git -C "$V" diff --name-only --diff-filter=U


path: scripts/_common.sh
content: #!/usr/bin/env bash
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKILL_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"

resolve_vault() {
	local override="${1:-}"
	local p=""
	if [[ -n "$override" && -d "$override" ]]; then
		(cd "$override" && pwd) && return 0
	fi
	if [[ -n "${OBSIDIAN_VAULT:-}" && -d "$OBSIDIAN_VAULT" ]]; then
		(cd "$OBSIDIAN_VAULT" && pwd) && return 0
	fi
	if [[ -n "${CURSOR_WORKSPACE:-}" && -d "${CURSOR_WORKSPACE}/.obsidian" ]]; then
		(cd "$CURSOR_WORKSPACE" && pwd) && return 0
	fi
	if [[ -d "${PWD}/.obsidian" ]]; then
		(cd "$PWD" && pwd) && return 0
	fi
	p="${HOME}/.openclaw/workspace/obsidian-git-vault"
	if [[ -d "$p" ]]; then
		(cd "$p" && pwd) && return 0
	fi
	return 1
}

require_git_repo() {
	local v="$1"
	git -C "$v" rev-parse --is-inside-work-tree >/dev/null 2>&1
}


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