# vault-sync

Adopt this skill: https://proskills.md/skills/other/graphlit-vault-sync

Page: https://proskills.md/skills/other/graphlit-vault-sync
This document is the public skill bundle for that page. It contains SKILL.md and every helper file the catalog carries. No login is required.

## File: plugins/vault-sync/skills/vault-sync-setup/SKILL.md
size: 2027
sha: ceeef92f42359dc0f4a5073ca3aabbb9378b0da0
upstream: https://github.com/graphlit/vault-sync/blob/HEAD/plugins/vault-sync/skills/vault-sync-setup/SKILL.md

````text
---
name: vault-sync-setup
description: Use when the user wants to install Vault Sync, connect an existing Vault Git repository to a local folder, refresh it, configure a recurring pull schedule, or make a local Vault available to an AI agent through filesystem files.
---

# Vault Sync Setup

Vault Sync mirrors an existing Vault Git repository to local files. It does not create Vaults, manage replicas, call app APIs, search, query, summarize, rank, index, or push changes.

## Workflow

1. Confirm the environment is macOS, Linux, or WSL2. For Windows users, recommend WSL2 and keep the Vault under the WSL filesystem.
2. Check for `bash` and `git`. `rg` is optional but useful for later consumption.
3. Install `vault-sync` if it is not already on `PATH`. Prefer the bundled script:

```bash
bash skills/vault-sync-setup/scripts/install-vault-sync.sh
```

If the bundled script is unavailable, install from GitHub:

```bash
mkdir -p ~/.local/bin
curl -fsSL https://raw.githubusercontent.com/graphlit/vault-sync/main/bin/vault-sync -o ~/.local/bin/vault-sync
chmod +x ~/.local/bin/vault-sync
```

4. Ask for the Vault Git repository URL and preferred local folder if missing. Use `~/vaults/<repo-name>` as the default.
5. Do not infer the local Vault path from the current working directory, app name, or repository owner. Do not invent hidden app folders such as `~/.zine/vault`, `~/.dossium/vault`, or `~/.graphlit/vault`.
6. Initialize and verify:

```bash
vault-sync init <vault-repo-url> <path>
vault-sync pull <path>
vault-sync doctor <path>
```

7. Ask before installing a recurring schedule. If approved:

```bash
vault-sync schedule install <path> --every 5m
```

## Agent Behavior

- Treat the Vault as generated, read-only context.
- Do not edit Vault files unless the user explicitly asks to modify local copies.
- Do not use `vault-sync` as a search or read API. After setup, use normal filesystem tools.
- If Git authentication fails, let the user complete their normal Git or GitHub CLI authentication flow.
````

## File: plugins/vault-sync/skills/vault-sync-use/SKILL.md
size: 1449
sha: 48d5b49122f987ddf4352e88d9e47644aefaaf97
upstream: https://github.com/graphlit/vault-sync/blob/HEAD/plugins/vault-sync/skills/vault-sync-use/SKILL.md

````text
---
name: vault-sync-use
description: Use when the user wants an AI agent to answer questions or perform research from an already-synced Vault folder using local Markdown files and normal filesystem tools.
---

# Vault Sync Use

Use an already-synced Vault as local, read-only context. Vault Sync provides files; the agent does the reading and reasoning.

## Workflow

1. If `vault-sync` is available and the user has not asked to avoid updates, refresh the Vault:

```bash
vault-sync pull <path>
```

2. Read `<path>/README.md` first to learn the Vault source and conventions.
3. Ignore hidden control folders: `.git`, `.zine`, `.dossium`, `.graphlit`, `.claude`, and `.codex`.
4. Search and read Markdown directly with normal filesystem tools:

```bash
rg -n "<terms>" <path>
ls <path>
sed -n '1,160p' <file>
```

5. Cite local file paths in responses when useful.

## Boundaries

- Do not call app APIs for Vault consumption.
- Do not use `vault-sync` for search, query, read, summarize, rank, or index.
- Do not edit generated Vault files unless the user explicitly asks for local modifications.
- If QMD is installed, it can be an optional local search helper. Add the Vault folder with `qmd collection add <path> --name <name> --mask "**/*.md"`, add context with `qmd context add qmd://<name> "Local Vault synced by Vault Sync"`, then run `qmd embed`. Use QMD to find likely files and local file reads for the full source. Do not require QMD.
````

## File: skill.md
size: 2319
sha: 22896e837a5bf8dd9541ba000d3db6b70390ecfd
upstream: https://github.com/graphlit/vault-sync/blob/HEAD/skill.md

````text
---
name: vault-sync
description: Use when a user wants an AI agent to install Vault Sync, mirror an existing Vault Git repository to local files, keep it refreshed, or consume an already-synced Vault with normal filesystem tools.
---

# Vault Sync

Vault Sync mirrors an existing Vault Git repository onto the user's local filesystem. Use it only for file sync and local readiness. Do not use it for search, query, chat, indexing, Vault creation, or Vault management.

## When Setting Up

1. Confirm the environment is macOS, Linux, or WSL2. Native Windows PowerShell and Command Prompt are out of scope.
2. Check for `bash` and `git`. `rg` is optional but useful after sync.
3. Install `vault-sync` if it is not already on `PATH`:

```bash
mkdir -p ~/.local/bin
curl -fsSL https://raw.githubusercontent.com/graphlit/vault-sync/main/bin/vault-sync -o ~/.local/bin/vault-sync
chmod +x ~/.local/bin/vault-sync
```

4. Ask for the Vault Git repository URL and local folder if the user did not provide them. Use `~/vaults/<repo-name>` as the default.
5. Do not infer the local Vault path from the current working directory, app name, or repository owner. Do not invent hidden app folders such as `~/.zine/vault`, `~/.dossium/vault`, or `~/.graphlit/vault`.
6. Run:

```bash
vault-sync init <vault-repo-url> <path>
vault-sync pull <path>
vault-sync doctor <path>
```

7. If the user wants recurring refreshes, install a pull schedule:

```bash
vault-sync schedule install <path> --every 5m
```

## When Using A Synced Vault

1. Refresh first when practical:

```bash
vault-sync pull <path>
```

2. Read `<path>/README.md` first.
3. Ignore hidden control folders such as `.git`, `.zine`, `.dossium`, `.graphlit`, `.claude`, and `.codex`.
4. Use ordinary filesystem tools: `ls`, `rg`, `sed`, `cat`, and native file reads.
5. Treat the Vault as read-only unless the user explicitly asks to edit local files.

## Optional QMD Search Companion

If QMD is installed and the user wants a stronger local search helper, add the synced Vault folder as a QMD collection:

```bash
qmd collection add <path> --name <name> --mask "**/*.md"
qmd context add qmd://<name> "Local Vault synced by Vault Sync"
qmd embed
```

Use QMD to find likely files; use file reads for the full source.

Vault Sync is the mirror. The agent is the reader.
````

## File: skills/vault-sync-setup/SKILL.md
size: 2027
sha: ceeef92f42359dc0f4a5073ca3aabbb9378b0da0
upstream: https://github.com/graphlit/vault-sync/blob/HEAD/skills/vault-sync-setup/SKILL.md

````text
---
name: vault-sync-setup
description: Use when the user wants to install Vault Sync, connect an existing Vault Git repository to a local folder, refresh it, configure a recurring pull schedule, or make a local Vault available to an AI agent through filesystem files.
---

# Vault Sync Setup

Vault Sync mirrors an existing Vault Git repository to local files. It does not create Vaults, manage replicas, call app APIs, search, query, summarize, rank, index, or push changes.

## Workflow

1. Confirm the environment is macOS, Linux, or WSL2. For Windows users, recommend WSL2 and keep the Vault under the WSL filesystem.
2. Check for `bash` and `git`. `rg` is optional but useful for later consumption.
3. Install `vault-sync` if it is not already on `PATH`. Prefer the bundled script:

```bash
bash skills/vault-sync-setup/scripts/install-vault-sync.sh
```

If the bundled script is unavailable, install from GitHub:

```bash
mkdir -p ~/.local/bin
curl -fsSL https://raw.githubusercontent.com/graphlit/vault-sync/main/bin/vault-sync -o ~/.local/bin/vault-sync
chmod +x ~/.local/bin/vault-sync
```

4. Ask for the Vault Git repository URL and preferred local folder if missing. Use `~/vaults/<repo-name>` as the default.
5. Do not infer the local Vault path from the current working directory, app name, or repository owner. Do not invent hidden app folders such as `~/.zine/vault`, `~/.dossium/vault`, or `~/.graphlit/vault`.
6. Initialize and verify:

```bash
vault-sync init <vault-repo-url> <path>
vault-sync pull <path>
vault-sync doctor <path>
```

7. Ask before installing a recurring schedule. If approved:

```bash
vault-sync schedule install <path> --every 5m
```

## Agent Behavior

- Treat the Vault as generated, read-only context.
- Do not edit Vault files unless the user explicitly asks to modify local copies.
- Do not use `vault-sync` as a search or read API. After setup, use normal filesystem tools.
- If Git authentication fails, let the user complete their normal Git or GitHub CLI authentication flow.
````

## File: skills/vault-sync-use/SKILL.md
size: 1449
sha: 48d5b49122f987ddf4352e88d9e47644aefaaf97
upstream: https://github.com/graphlit/vault-sync/blob/HEAD/skills/vault-sync-use/SKILL.md

````text
---
name: vault-sync-use
description: Use when the user wants an AI agent to answer questions or perform research from an already-synced Vault folder using local Markdown files and normal filesystem tools.
---

# Vault Sync Use

Use an already-synced Vault as local, read-only context. Vault Sync provides files; the agent does the reading and reasoning.

## Workflow

1. If `vault-sync` is available and the user has not asked to avoid updates, refresh the Vault:

```bash
vault-sync pull <path>
```

2. Read `<path>/README.md` first to learn the Vault source and conventions.
3. Ignore hidden control folders: `.git`, `.zine`, `.dossium`, `.graphlit`, `.claude`, and `.codex`.
4. Search and read Markdown directly with normal filesystem tools:

```bash
rg -n "<terms>" <path>
ls <path>
sed -n '1,160p' <file>
```

5. Cite local file paths in responses when useful.

## Boundaries

- Do not call app APIs for Vault consumption.
- Do not use `vault-sync` for search, query, read, summarize, rank, or index.
- Do not edit generated Vault files unless the user explicitly asks for local modifications.
- If QMD is installed, it can be an optional local search helper. Add the Vault folder with `qmd collection add <path> --name <name> --mask "**/*.md"`, add context with `qmd context add qmd://<name> "Local Vault synced by Vault Sync"`, then run `qmd embed`. Use QMD to find likely files and local file reads for the full source. Do not require QMD.
````

## File: README.md
size: 7585
sha: 41a3ffc0aac76c314a7ec75c75252c5c763d1294
upstream: https://github.com/graphlit/vault-sync/blob/HEAD/README.md

````text
# Vault Sync

Vault Sync keeps a Vault Git repository synced to your computer.

A Vault is a Git repository of Markdown files: notes, source material, transcripts, summaries, observations, and other context exported from your app. Vault Sync keeps those files fresh locally so AI tools can read them without needing a live app connection.

## Who It Helps

Personal knowledge Vaults can keep everyday context available to local AI tools:

- students with lecture notes, readings, and research papers
- researchers cross-referencing papers, citations, and notes
- freelancers juggling client docs, project notes, and emails
- homemakers organizing recipes, household docs, school info, and planning
- job seekers tracking resumes, cover letters, postings, and interview prep
- hobbyists and makers collecting tutorials, references, and project logs
- writers working across drafts, research, notes, and source material

Team and business Vaults can keep operational context available locally:

- engineering and product teams reviewing incident context and feature history
- account executives preparing for calls and relationship updates
- support teams researching incidents, escalations, and account history
- leaders preparing for board meetings, partner syncs, or investor updates
- customer success teams preparing QBRs and account reviews
- deal teams and investors reviewing diligence material and portfolio context

## What It Does

- clones an existing Vault repository to your computer
- pulls updates on demand
- can install a recurring local pull schedule
- reports sync status and local health
- keeps the Vault ready for file-based AI tools

## What It Does Not Do

- does not create or manage your Vault
- does not call back to the app that generated the Vault
- does not search, chat, summarize, rank, or index
- does not edit generated Vault files
- does not push changes back to the Vault repository

Vault Sync is the mirror. Your AI tool is the reader.

## Supported Platforms

Vault Sync currently supports:

- macOS
- Linux
- WSL2 Ubuntu and similar WSL2 Linux distributions

Native Windows PowerShell and Command Prompt usage are not currently supported. If you use Windows, run Vault Sync inside WSL2 and keep the Vault under the WSL filesystem, such as `~/vaults/<name>`, especially when your AI tool also runs in WSL2.

## Agent-First Install

Vault Sync is designed so an AI agent can set up the local mirror with your approval. You provide the Vault repository URL from your app; the agent installs the small `vault-sync` script, clones the repository, and optionally configures a recurring pull schedule.

If your agent supports skill installation from GitHub, install the setup and usage skills:

```bash
npx skills add graphlit/vault-sync --skill vault-sync-setup
npx skills add graphlit/vault-sync --skill vault-sync-use
```

Then ask your agent:

```text
Set up Vault Sync for my Vault at <your-vault-repo-url>.
```

For agents that support direct skill URLs, point them at:

```text
https://raw.githubusercontent.com/graphlit/vault-sync/main/skill.md
```

App-hosted skill entrypoints can also be served at:

```text
https://www.zine.ai/skill.md
https://www.dossium.ai/skill.md
```

Codex CLI `0.121.0` or newer can also consume this repository as a plugin marketplace:

```bash
codex plugin marketplace add graphlit/vault-sync
```

After install, Codex refers to the marketplace by the name in this repo's metadata:

```bash
codex plugin marketplace upgrade graphlit-vault-sync
codex plugin marketplace remove graphlit-vault-sync
```

## Install

Clone this repository and install the script somewhere on your `PATH`:

```bash
git clone https://github.com/graphlit/vault-sync
cd vault-sync
install -m 0755 bin/vault-sync ~/.local/bin/vault-sync
```

If `~/.local/bin` is not on your `PATH`, either add it or run the script directly from this checkout:

```bash
bash bin/vault-sync status ~/vaults/my-vault
```

## Quick Start

Use the Vault repository URL from your app:

```bash
vault-sync init <your-vault-repo-url> ~/vaults/my-vault
vault-sync pull ~/vaults/my-vault
vault-sync status ~/vaults/my-vault
```

Then point your AI tool at the local folder:

```text
~/vaults/my-vault
```

Ask your tool to inspect the Markdown files directly. For example, in Claude Code, Codex, OpenClaw, Cursor, or VS Code:

```text
Use the local Vault in ~/vaults/my-vault to answer this. Start by reading README.md, then search the Markdown files.
```

You can also search the files yourself:

```bash
rg -n "your search terms" ~/vaults/my-vault
```

## Commands

```bash
vault-sync init <repo-url> [path]
vault-sync pull [path]
vault-sync status [path]
vault-sync schedule install [path] --every 5m
vault-sync schedule remove [path]
vault-sync doctor [path]
```

The command surface is intentionally small. Search and reading are handled by your AI tool's normal file access.

## Working With AI Tools

Vault Sync works well with file-aware tools such as:

- Claude Code
- Codex
- OpenClaw
- Cursor
- VS Code

Suggested prompt:

```text
The Vault is a local folder of Markdown files. Read README.md first. Ignore hidden control folders like .git, .zine, .dossium, .graphlit, .claude, and .codex. Use normal file tools to search and read the Markdown files.
```

## Optional: Search Companion With QMD

[QMD](https://github.com/tobi/qmd) is a local search companion for folders full of Markdown. Vault Sync and QMD fit together well:

- Vault Sync keeps the Vault Git repository current on disk.
- QMD can make the synced Markdown easier to search.
- Your agent can still open the actual files whenever it needs the full source.

The workflow is simple: sync the Vault first, then let QMD search the local folder. Install QMD separately, then add the local Vault folder:

```bash
npm install -g @tobilu/qmd

vault-sync pull ~/vaults/my-vault
qmd collection add ~/vaults/my-vault --name my-vault --mask "**/*.md"
qmd context add qmd://my-vault "Local Vault synced by Vault Sync"
qmd embed
```

Then agents can use QMD as an optional search helper:

```bash
qmd query "customer escalation patterns" -c my-vault --json -n 10
qmd get "path/from/result.md" --full
```

QMD is not required. Vault Sync does not install, configure, or depend on QMD; it just keeps the local files fresh so tools like QMD can use them.

## Keeping It Fresh

Pull updates manually:

```bash
vault-sync pull ~/vaults/my-vault
```

Or install a recurring pull schedule:

```bash
vault-sync schedule install ~/vaults/my-vault --every 5m
```

Remove the schedule later:

```bash
vault-sync schedule remove ~/vaults/my-vault
```

## Security

- plain Bash script
- no opaque binary
- no credentials stored by Vault Sync
- Git authentication stays in your existing Git or GitHub setup
- scheduled jobs only run `vault-sync pull <path>`
- local Vault files are not uploaded by this tool

## Troubleshooting

Run:

```bash
vault-sync doctor ~/vaults/my-vault
```

This checks for Git, repository health, local changes, remote reachability, optional `rg`, and scheduler state.

## Agent Install Notes

See:

- [Agent Install Guide](docs/agent-install-guide.md)
- [Claude Code](examples/install-claude-code.md)
- [Codex](examples/install-codex.md)
- [OpenClaw](examples/install-openclaw.md)

These examples are intentionally focused on agent setup and consumption. Vault creation and management stay in the app that generated the Vault.

## Development

Run the smoke test with:

```bash
bash tests/smoke.sh
```

The test creates a temporary local Git repository and does not touch your real Vaults.
````

## File: .agents/plugins/marketplace.json
size: 379
sha: ed3accd8f137661657957c07a3b182288853fc87
upstream: https://github.com/graphlit/vault-sync/blob/HEAD/.agents/plugins/marketplace.json

```text
{
  "name": "graphlit-vault-sync",
  "interface": {
    "displayName": "Vault Sync"
  },
  "plugins": [
    {
      "name": "vault-sync",
      "source": {
        "source": "local",
        "path": "./plugins/vault-sync"
      },
      "policy": {
        "installation": "AVAILABLE",
        "authentication": "ON_INSTALL"
      },
      "category": "Productivity"
    }
  ]
}
```

## File: bin/vault-sync
size: 17882
sha: 3828acb076758d341883ace9158dd25036c80884
upstream: https://github.com/graphlit/vault-sync/blob/HEAD/bin/vault-sync

```text
#!/usr/bin/env bash
set -euo pipefail

VERSION="0.1.0"
APP_NAME="vault-sync"
CONFIG_DIR="${VAULT_SYNC_CONFIG_DIR:-$HOME/.config/vault-sync}"
STATE_DIR="$CONFIG_DIR/vaults"
LOCK_DIR="$CONFIG_DIR/locks"
SCHEDULE_DIR="$CONFIG_DIR/schedules"
LOG_DIR="${VAULT_SYNC_LOG_DIR:-$CONFIG_DIR/logs}"
ACTIVE_LOCK=""

die() {
  printf 'vault-sync: %s\n' "$*" >&2
  exit 1
}

note() {
  printf '%s\n' "$*"
}

usage() {
  cat <<'EOF'
vault-sync keeps an existing Vault Git repository mirrored onto the local filesystem.

Usage:
  vault-sync init <repo-url> [path]
  vault-sync pull [path]
  vault-sync status [path] [--json]
  vault-sync schedule install [path] [--every 5m]
  vault-sync schedule remove [path]
  vault-sync doctor [path]
  vault-sync version

Vault Sync mirrors files only. It does not search, query, read, summarize, rank,
index, call Graphlit APIs, create Vaults, or push changes.
EOF
}

ensure_dirs() {
  mkdir -p "$STATE_DIR" "$LOCK_DIR" "$SCHEDULE_DIR" "$LOG_DIR"
}

require_command() {
  command -v "$1" >/dev/null 2>&1 || die "Required command [$1] was not found."
}

expand_path() {
  case "${1:-}" in
    "~") printf '%s\n' "$HOME" ;;
    "~/"*) printf '%s/%s\n' "$HOME" "${1#~/}" ;;
    "") printf '%s\n' "" ;;
    *) printf '%s\n' "$1" ;;
  esac
}

absolute_path() {
  local raw parent base
  raw="$(expand_path "$1")"
  parent="$(dirname "$raw")"
  base="$(basename "$raw")"
  mkdir -p "$parent"
  parent="$(cd "$parent" && pwd -P)"
  printf '%s/%s\n' "$parent" "$base"
}

script_path() {
  local source_dir
  source_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
  printf '%s/%s\n' "$source_dir" "$(basename "${BASH_SOURCE[0]}")"
}

repo_name_from_url() {
  local url name
  url="${1%/}"
  name="${url##*/}"
  name="${name%.git}"
  [[ -n "$name" ]] || die "Could not infer a repository name from [$1]."
  printf '%s\n' "$name"
}

default_vault_path() {
  printf '%s/Vaults/%s\n' "$HOME" "$(repo_name_from_url "$1")"
}

state_id_for_path() {
  local path checksum base
  path="$1"
  checksum="$(printf '%s' "$path" | cksum | awk '{print $1}')"
  base="$(basename "$path" | tr -c 'A-Za-z0-9._-' '_')"
  printf '%s-%s\n' "$checksum" "$base"
}

state_file_for_path() {
  printf '%s/%s.env\n' "$STATE_DIR" "$(state_id_for_path "$1")"
}

state_get() {
  local path key file
  path="$1"
  key="$2"
  file="$(state_file_for_path "$path")"
  [[ -f "$file" ]] || return 0
  awk -F= -v key="$key" '$1 == key { sub(/^[^=]*=/, ""); print; exit }' "$file"
}

state_set() {
  local path key value file temp
  path="$1"
  key="$2"
  value="$3"
  ensure_dirs
  file="$(state_file_for_path "$path")"
  temp="$file.tmp.$$"
  if [[ -f "$file" ]]; then
    awk -F= -v key="$key" '$1 != key { print }' "$file" >"$temp"
  else
    : >"$temp"
  fi
  printf '%s=%s\n' "$key" "$value" >>"$temp"
  mv "$temp" "$file"
}

state_paths() {
  ensure_dirs
  find "$STATE_DIR" -type f -name '*.env' -print 2>/dev/null | while IFS= read -r file; do
    awk -F= '$1 == "path" { sub(/^[^=]*=/, ""); print; exit }' "$file"
  done
}

normalize_remote() {
  local remote
  remote="${1%/}"
  remote="${remote%.git}"
  printf '%s\n' "$remote"
}

remote_matches() {
  [[ "$(normalize_remote "$1")" == "$(normalize_remote "$2")" ]]
}

require_git_repo() {
  local path="$1"
  [[ -d "$path/.git" ]] || die "Path [$path] is not a Git repository."
}

worktree_dirty() {
  local path="$1"
  [[ -n "$(git -C "$path" status --porcelain)" ]]
}

markdown_count() {
  local path="$1"
  find "$path" -type f -name '*.md' \
    ! -path '*/.git/*' \
    ! -path '*/.zine/*' \
    ! -path '*/.dossium/*' \
    ! -path '*/.graphlit/*' \
    2>/dev/null | wc -l | tr -d ' '
}

resolve_vault_path() {
  local provided paths count path_line
  provided="${1:-}"
  if [[ -n "$provided" ]]; then
    absolute_path "$provided"
    return
  fi

  if [[ -d ".git" ]]; then
    pwd -P
    return
  fi

  paths=()
  while IFS= read -r path_line; do
    paths+=("$path_line")
  done < <(state_paths)

  count="${#paths[@]}"
  if [[ "$count" -eq 1 ]]; then
    printf '%s\n' "${paths[0]}"
    return
  fi

  if [[ "$count" -gt 1 ]]; then
    die "Multiple Vaults are configured. Pass a path explicitly."
  fi

  die "No Vault path was provided and no configured Vault was found."
}

json_escape() {
  local value="$1"
  value="${value//\\/\\\\}"
  value="${value//\"/\\\"}"
  value="${value//$'\n'/\\n}"
  value="${value//$'\r'/\\r}"
  printf '%s\n' "$value"
}

interval_to_seconds() {
  local value number unit
  value="${1:-5m}"
  if [[ "$value" =~ ^([0-9]+)([smhd]?)$ ]]; then
    number="${BASH_REMATCH[1]}"
    unit="${BASH_REMATCH[2]:-s}"
    case "$unit" in
      s) printf '%s\n' "$number" ;;
      m) printf '%s\n' "$((number * 60))" ;;
      h) printf '%s\n' "$((number * 3600))" ;;
      d) printf '%s\n' "$((number * 86400))" ;;
      *) die "Unsupported interval [$value]." ;;
    esac
  else
    die "Unsupported interval [$value]. Use values such as [5m], [30s], or [1h]."
  fi
}

interval_to_cron_minutes() {
  local seconds="$1"
  if (( seconds < 60 )); then
    die "Cron fallback requires an interval of at least 1 minute."
  fi
  if (( seconds % 60 != 0 )); then
    die "Cron fallback requires a whole-minute interval."
  fi
  if (( seconds > 3540 )); then
    die "Cron fallback supports intervals up to 59 minutes."
  fi
  printf '%s\n' "$((seconds / 60))"
}

schedule_id_for_path() {
  printf 'vault-sync-%s\n' "$(state_id_for_path "$1")"
}

make_schedule_wrapper() {
  local path interval_seconds id wrapper script jitter log script_arg path_arg log_arg
  path="$1"
  interval_seconds="$2"
  id="$(schedule_id_for_path "$path")"
  wrapper="$SCHEDULE_DIR/$id.sh"
  script="$(script_path)"
  jitter="$(( $(printf '%s' "$path" | cksum | awk '{print $1}') % 30 ))"
  log="$LOG_DIR/$id.log"
  printf -v script_arg '%q' "$script"
  printf -v path_arg '%q' "$path"
  printf -v log_arg '%q' "$log"

  ensure_dirs
  cat >"$wrapper" <<EOF
#!/usr/bin/env bash
set -euo pipefail
sleep "$jitter"
exec $script_arg pull $path_arg >>$log_arg 2>&1
EOF
  chmod +x "$wrapper"
  note "$wrapper"
}

install_launchd_schedule() {
  local path interval_seconds id wrapper plist label
  path="$1"
  interval_seconds="$2"
  id="$(schedule_id_for_path "$path")"
  wrapper="$(make_schedule_wrapper "$path" "$interval_seconds")"
  label="com.graphlit.$id"
  plist="$HOME/Library/LaunchAgents/$label.plist"
  mkdir -p "$HOME/Library/LaunchAgents"

  cat >"$plist" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>$label</string>
  <key>ProgramArguments</key>
  <array>
    <string>/bin/bash</string>
    <string>$wrapper</string>
  </array>
  <key>StartInterval</key>
  <integer>$interval_seconds</integer>
  <key>RunAtLoad</key>
  <true/>
</dict>
</plist>
EOF

  launchctl unload "$plist" >/dev/null 2>&1 || true
  launchctl load "$plist"
  state_set "$path" scheduler "launchd"
  state_set "$path" scheduler_id "$id"
  note "Installed launchd schedule [$label] for [$path]."
}

systemd_user_available() {
  command -v systemctl >/dev/null 2>&1 && systemctl --user status >/dev/null 2>&1
}

install_systemd_schedule() {
  local path interval_seconds id wrapper unit timer unit_dir
  path="$1"
  interval_seconds="$2"
  id="$(schedule_id_for_path "$path")"
  wrapper="$(make_schedule_wrapper "$path" "$interval_seconds")"
  unit_dir="$HOME/.config/systemd/user"
  unit="$unit_dir/$id.service"
  timer="$unit_dir/$id.timer"
  mkdir -p "$unit_dir"

  cat >"$unit" <<EOF
[Unit]
Description=Vault Sync pull for $path

[Service]
Type=oneshot
ExecStart=/bin/bash $wrapper
EOF

  cat >"$timer" <<EOF
[Unit]
Description=Vault Sync timer for $path

[Timer]
OnBootSec=1min
OnUnitActiveSec=${interval_seconds}s
Unit=$id.service

[Install]
WantedBy=timers.target
EOF

  systemctl --user daemon-reload
  systemctl --user enable --now "$id.timer"
  state_set "$path" scheduler "systemd"
  state_set "$path" scheduler_id "$id"
  note "Installed systemd user timer [$id.timer] for [$path]."
}

install_cron_schedule() {
  local path interval_seconds minutes id wrapper temp
  path="$1"
  interval_seconds="$2"
  minutes="$(interval_to_cron_minutes "$interval_seconds")"
  id="$(schedule_id_for_path "$path")"
  wrapper="$(make_schedule_wrapper "$path" "$interval_seconds")"
  temp="$(mktemp)"

  if crontab -l >/dev/null 2>&1; then
    crontab -l | grep -v "# $id\$" >"$temp" || true
  else
    : >"$temp"
  fi

  printf '*/%s * * * * /bin/bash %q # %s\n' "$minutes" "$wrapper" "$id" >>"$temp"
  crontab "$temp"
  rm -f "$temp"
  state_set "$path" scheduler "cron"
  state_set "$path" scheduler_id "$id"
  note "Installed cron schedule [$id] for [$path]."
}

remove_launchd_schedule() {
  local path id label plist
  path="$1"
  id="$(schedule_id_for_path "$path")"
  label="com.graphlit.$id"
  plist="$HOME/Library/LaunchAgents/$label.plist"
  if [[ -f "$plist" ]]; then
    launchctl unload "$plist" >/dev/null 2>&1 || true
    rm -f "$plist"
  fi
}

remove_systemd_schedule() {
  local path id unit_dir
  path="$1"
  id="$(schedule_id_for_path "$path")"
  unit_dir="$HOME/.config/systemd/user"
  if command -v systemctl >/dev/null 2>&1; then
    systemctl --user disable --now "$id.timer" >/dev/null 2>&1 || true
  fi
  rm -f "$unit_dir/$id.service" "$unit_dir/$id.timer"
  if command -v systemctl >/dev/null 2>&1; then
    systemctl --user daemon-reload >/dev/null 2>&1 || true
  fi
}

remove_cron_schedule() {
  local path id temp
  path="$1"
  id="$(schedule_id_for_path "$path")"
  temp="$(mktemp)"
  if crontab -l >/dev/null 2>&1; then
    crontab -l | grep -v "# $id\$" >"$temp" || true
    crontab "$temp"
  fi
  rm -f "$temp"
}

remove_schedule() {
  local path id
  path="$1"
  id="$(schedule_id_for_path "$path")"
  remove_launchd_schedule "$path"
  remove_systemd_schedule "$path"
  remove_cron_schedule "$path"
  rm -f "$SCHEDULE_DIR/$id.sh"
  state_set "$path" scheduler ""
  state_set "$path" scheduler_id ""
  note "Removed schedule for [$path]."
}

acquire_lock() {
  local path id lock
  path="$1"
  ensure_dirs
  id="$(state_id_for_path "$path")"
  lock="$LOCK_DIR/$id.lock"
  if ! mkdir "$lock" 2>/dev/null; then
    die "Another vault-sync process is already running for [$path]."
  fi
  ACTIVE_LOCK="$lock"
  trap 'rm -rf "$ACTIVE_LOCK"' EXIT
}

cmd_init() {
  local repo_url path remote
  repo_url="${1:-}"
  [[ -n "$repo_url" ]] || die "Usage: vault-sync init <repo-url> [path]"
  path="$(absolute_path "${2:-$(default_vault_path "$repo_url")}")"
  require_command git
  ensure_dirs

  if [[ -d "$path/.git" ]]; then
    remote="$(git -C "$path" config --get remote.origin.url || true)"
    [[ -n "$remote" ]] || die "Existing repository [$path] has no origin remote."
    remote_matches "$remote" "$repo_url" || die "Existing repository [$path] points at [$remote], not [$repo_url]."
    note "Using existing Vault mirror [$path]."
  else
    if [[ -e "$path" ]] && [[ -n "$(find "$path" -mindepth 1 -maxdepth 1 -print -quit 2>/dev/null)" ]]; then
      die "Path [$path] exists and is not an empty Git repository."
    fi
    mkdir -p "$(dirname "$path")"
    git clone "$repo_url" "$path"
    note "Cloned Vault repository to [$path]."
  fi

  state_set "$path" path "$path"
  state_set "$path" repo_url "$repo_url"
  state_set "$path" initialized_at "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
  note "Vault configured. Run [vault-sync pull \"$path\"] to refresh it."
}

cmd_pull() {
  local path commit
  path="$(resolve_vault_path "${1:-}")"
  require_command git
  require_git_repo "$path"
  acquire_lock "$path"

  if worktree_dirty "$path"; then
    die "Refusing to pull because [$path] has local changes."
  fi

  git -C "$path" fetch --prune
  git -C "$path" pull --ff-only
  commit="$(git -C "$path" rev-parse HEAD)"
  state_set "$path" path "$path"
  state_set "$path" repo_url "$(git -C "$path" config --get remote.origin.url || true)"
  state_set "$path" last_success "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
  state_set "$path" last_commit "$commit"
  note "Vault [$path] is current at [$commit]."
}

cmd_status() {
  local path json remote branch commit dirty last_success scheduler count
  json="false"
  path=""
  while [[ "$#" -gt 0 ]]; do
    case "$1" in
      --json) json="true" ;;
      *) path="$1" ;;
    esac
    shift
  done

  path="$(resolve_vault_path "$path")"
  require_git_repo "$path"
  remote="$(git -C "$path" config --get remote.origin.url || true)"
  branch="$(git -C "$path" symbolic-ref --short HEAD 2>/dev/null || git -C "$path" rev-parse --short HEAD)"
  commit="$(git -C "$path" rev-parse HEAD)"
  dirty="false"
  worktree_dirty "$path" && dirty="true"
  last_success="$(state_get "$path" last_success || true)"
  scheduler="$(state_get "$path" scheduler || true)"
  count="$(markdown_count "$path")"

  if [[ "$json" == "true" ]]; then
    printf '{\n'
    printf '  "path": "%s",\n' "$(json_escape "$path")"
    printf '  "remote": "%s",\n' "$(json_escape "$remote")"
    printf '  "branch": "%s",\n' "$(json_escape "$branch")"
    printf '  "commit": "%s",\n' "$(json_escape "$commit")"
    printf '  "dirty": %s,\n' "$dirty"
    printf '  "lastSuccessfulSync": "%s",\n' "$(json_escape "$last_success")"
    printf '  "scheduler": "%s",\n' "$(json_escape "$scheduler")"
    printf '  "markdownFileCount": %s\n' "$count"
    printf '}\n'
    return
  fi

  note "Path: $path"
  note "Remote: $remote"
  note "Branch: $branch"
  note "Commit: $commit"
  note "Dirty: $dirty"
  note "Last successful sync: ${last_success:-never}"
  note "Scheduler: ${scheduler:-not installed}"
  note "Markdown files: $count"
}

cmd_schedule_install() {
  local path interval interval_seconds os
  path=""
  interval="5m"
  while [[ "$#" -gt 0 ]]; do
    case "$1" in
      --every)
        shift
        interval="${1:-}"
        [[ -n "$interval" ]] || die "Missing value for [--every]."
        ;;
      *) path="$1" ;;
    esac
    shift
  done

  path="$(resolve_vault_path "$path")"
  require_git_repo "$path"
  interval_seconds="$(interval_to_seconds "$interval")"
  os="$(uname -s)"

  case "$os" in
    Darwin)
      require_command launchctl
      install_launchd_schedule "$path" "$interval_seconds"
      ;;
    Linux)
      if systemd_user_available; then
        install_systemd_schedule "$path" "$interval_seconds"
      else
        require_command crontab
        install_cron_schedule "$path" "$interval_seconds"
      fi
      ;;
    *)
      die "Unsupported platform [$os]. Use macOS, Linux, or WSL2."
      ;;
  esac
}

cmd_schedule_remove() {
  local path
  path="$(resolve_vault_path "${1:-}")"
  remove_schedule "$path"
}

doctor_line() {
  local level message
  level="$1"
  message="$2"
  printf '[%s] %s\n' "$level" "$message"
}

cmd_doctor() {
  local raw_path path failures remote os first_path
  raw_path="${1:-}"
  failures=0
  os="$(uname -s)"

  case "$os" in
    Darwin|Linux) doctor_line OK "Platform [$os] is supported." ;;
    *) doctor_line FAIL "Platform [$os] is unsupported. Use macOS, Linux, or WSL2."; failures=$((failures + 1)) ;;
  esac

  if command -v git >/dev/null 2>&1; then
    doctor_line OK "git is installed."
  else
    doctor_line FAIL "git is required."
    failures=$((failures + 1))
  fi

  if command -v rg >/dev/null 2>&1; then
    doctor_line OK "rg is installed."
  else
    doctor_line WARN "rg is recommended for Vault consumption."
  fi

  if command -v gh >/dev/null 2>&1; then
    doctor_line OK "gh is installed."
  else
    doctor_line WARN "gh is optional and only helps diagnose GitHub authentication."
  fi

  first_path="$(state_paths | head -n 1 || true)"
  if [[ -n "$raw_path" ]] || [[ -d ".git" ]] || [[ -n "$first_path" ]]; then
    path="$(resolve_vault_path "$raw_path")"
    if [[ -d "$path/.git" ]]; then
      doctor_line OK "Vault path [$path] is a Git repository."
      if worktree_dirty "$path"; then
        doctor_line FAIL "Vault path [$path] has local changes."
        failures=$((failures + 1))
      else
        doctor_line OK "Vault worktree is clean."
      fi
      remote="$(git -C "$path" config --get remote.origin.url || true)"
      if [[ -n "$remote" ]]; then
        doctor_line OK "Origin remote is [$remote]."
        if git ls-remote "$remote" HEAD >/dev/null 2>&1; then
          doctor_line OK "Origin remote is reachable."
        else
          doctor_line WARN "Origin remote was not reachable from this environment."
        fi
      else
        doctor_line FAIL "No origin remote configured."
        failures=$((failures + 1))
      fi
      if [[ -n "$(state_get "$path" scheduler || true)" ]]; then
        doctor_line OK "Scheduler is recorded as [$(state_get "$path" scheduler)]."
      else
        doctor_line WARN "No scheduler is recorded for this Vault."
      fi
    else
      doctor_line WARN "Vault path [$path] does not exist yet or is not a Git repository."
    fi
  else
    doctor_line WARN "No Vault path provided and no configured Vault was found."
  fi

  if [[ "$failures" -gt 0 ]]; then
    exit 1
  fi
}

main() {
  local command="${1:-}"
  if [[ -z "$command" ]]; then
    usage
    exit 1
  fi
  shift || true

  case "$command" in
    init) cmd_init "$@" ;;
    pull) cmd_pull "$@" ;;
    status) cmd_status "$@" ;;
    schedule)
      case "${1:-}" in
        install) shift; cmd_schedule_install "$@" ;;
        remove) shift; cmd_schedule_remove "$@" ;;
        *) die "Usage: vault-sync schedule install|remove [path]" ;;
      esac
      ;;
    doctor) cmd_doctor "$@" ;;
    version|--version|-v) note "$APP_NAME $VERSION" ;;
    help|--help|-h) usage ;;
    search|query|read|summarize|rank|index)
      die "[$command] is intentionally unsupported. Use filesystem tools such as [rg], [ls], [cat], and [sed]."
      ;;
    *) die "Unknown command [$command]. Run [vault-sync help]." ;;
  esac
}

main "$@"
```

## File: docs/agent-install-guide.md
size: 5905
sha: 8a1af17bda9572216b8aca7e2efdba2f8964c459
upstream: https://github.com/graphlit/vault-sync/blob/HEAD/docs/agent-install-guide.md

````text
# Agent Install Guide

This guide explains how to make Vault Sync available to Codex, Claude Code, and OpenClaw. Vault Sync is only for consuming an existing Vault Git repository as local files. Vault creation and management stay in the app that generated the Vault.

## What You Need

- A Vault Git repository URL.
- A local destination folder, such as `~/vaults/my-vault`.
- macOS, Linux, or WSL2.
- Git authentication already set up for the Vault repository.

Native Windows PowerShell and Command Prompt are not currently supported. On Windows, run the agent and Vault Sync inside WSL2, and keep the Vault under the WSL filesystem.

## Recommended Paths

Use the most native install path your agent supports:

| Agent | Recommended path | Fallback |
|---|---|---|
| Codex | Plugin marketplace in Codex CLI `0.121.0` or newer | Skills or manual CLI |
| Claude Code | Agent skills | Direct skill URL or manual CLI |
| OpenClaw | Agent skills or direct skill URL | Manual CLI |

The agent should set up the local mirror, then use normal filesystem tools to read it. It should not use Vault Sync as a search or query layer.

## Codex

### Plugin Marketplace

This path requires Codex CLI `0.121.0` or newer.

Add the Vault Sync marketplace:

```bash
codex plugin marketplace add graphlit/vault-sync
```

After install, use the marketplace name `graphlit-vault-sync` for maintenance:

```bash
codex plugin marketplace upgrade graphlit-vault-sync
codex plugin marketplace remove graphlit-vault-sync
```

Then ask Codex:

```text
Use the Vault Sync plugin to set up my Vault from <your-vault-repo-url>.
```

Codex should install `vault-sync`, initialize the local folder, run `vault-sync doctor`, and ask before installing a recurring pull schedule.

### Skills Fallback

If plugin marketplace support is not available in your Codex CLI version, install the skills directly:

```bash
npx skills add graphlit/vault-sync --skill vault-sync-setup
npx skills add graphlit/vault-sync --skill vault-sync-use
```

Then ask Codex:

```text
Use Vault Sync to set up my Vault from <your-vault-repo-url>.
```

### Direct Skill URL

If Codex supports reading a skill from a URL, use one of:

```text
https://raw.githubusercontent.com/graphlit/vault-sync/main/skill.md
https://www.zine.ai/skill.md
https://www.dossium.ai/skill.md
```

## Claude Code

### Agent Skills

Install the setup and usage skills:

```bash
npx skills add graphlit/vault-sync --skill vault-sync-setup
npx skills add graphlit/vault-sync --skill vault-sync-use
```

Then ask Claude Code:

```text
Set up Vault Sync for my Vault from <your-vault-repo-url>.
```

Claude Code should install `vault-sync`, initialize the local folder, run `vault-sync doctor`, and ask before installing a recurring pull schedule.

### Direct Skill URL

If your Claude Code environment supports direct skill URLs, use one of:

```text
https://raw.githubusercontent.com/graphlit/vault-sync/main/skill.md
https://www.zine.ai/skill.md
https://www.dossium.ai/skill.md
```

## OpenClaw

### Skill Install

If OpenClaw supports GitHub-based skill installation, install:

```bash
npx skills add graphlit/vault-sync --skill vault-sync-setup
npx skills add graphlit/vault-sync --skill vault-sync-use
```

Then ask OpenClaw:

```text
Set up Vault Sync for my Vault from <your-vault-repo-url>.
```

### Direct Skill URL

If OpenClaw supports direct skill URLs, use one of:

```text
https://raw.githubusercontent.com/graphlit/vault-sync/main/skill.md
https://www.zine.ai/skill.md
https://www.dossium.ai/skill.md
```

## Manual Fallback

Use this path when an agent cannot install skills or plugins directly:

```bash
git clone https://github.com/graphlit/vault-sync
cd vault-sync
install -m 0755 bin/vault-sync ~/.local/bin/vault-sync

vault-sync init <your-vault-repo-url> ~/vaults/<name>
vault-sync pull ~/vaults/<name>
vault-sync doctor ~/vaults/<name>
```

To keep the Vault fresh:

```bash
vault-sync schedule install ~/vaults/<name> --every 5m
```

## After Install

Once the Vault is local, point the agent at the folder and say:

```text
Use the local Vault in ~/vaults/<name>. Read README.md first. Ignore hidden control folders like .git, .zine, .dossium, .graphlit, .claude, and .codex. Use normal file tools to search and read the Markdown files.
```

The agent can use `ls`, `rg`, `sed`, `cat`, and native file reads. Vault Sync should only be used to initialize, pull, check status, schedule pulls, or run diagnostics.

## Optional: Search Companion With QMD

[QMD](https://github.com/tobi/qmd) can make a synced Vault folder easier to search. The roles are separate:

- Vault Sync handles Git clone, pull, status, schedule, and diagnostics.
- QMD searches the local Markdown files after they exist on disk.
- The agent should use QMD to find likely files, then open the files directly when it needs the full source.

If QMD is installed, add the Vault folder as a collection:

```bash
vault-sync pull ~/vaults/<name>
qmd collection add ~/vaults/<name> --name <name> --mask "**/*.md"
qmd context add qmd://<name> "Local Vault synced by Vault Sync"
qmd embed
```

Useful QMD commands for agents:

```bash
qmd search "<keywords>" -c <name> --json -n 10
qmd vsearch "<natural-language question>" -c <name> --json -n 10
qmd query "<research question>" -c <name> --json -n 10
qmd get "path/from/result.md" --full
```

QMD is optional. Do not install or require it unless the user asks for a stronger local search companion.

## Validation Checklist

After setup, run:

```bash
vault-sync status ~/vaults/<name>
vault-sync doctor ~/vaults/<name>
rg -n "<known term>" ~/vaults/<name>
```

Expected result:

- `vault-sync status` reports the current local revision.
- `vault-sync doctor` reports Git and repository health.
- `rg` can find text in Markdown files.

If Git authentication fails, authenticate through your normal Git or GitHub CLI flow and retry `vault-sync pull`.
````

## File: examples/install-claude-code.md
size: 1234
sha: 4250b1b0eda45092e05d42ca084c798488240497
upstream: https://github.com/graphlit/vault-sync/blob/HEAD/examples/install-claude-code.md

````text
# Claude Code Install

Use this when a Vault already exists in Git and Claude Code can access your local filesystem.

## Preferred: Agent Skills

Install the setup and usage skills:

```bash
npx skills add graphlit/vault-sync --skill vault-sync-setup
npx skills add graphlit/vault-sync --skill vault-sync-use
```

Then ask Claude Code:

```text
Set up Vault Sync for my Vault from <your-vault-repo-url>.
```

Claude Code should install `vault-sync`, initialize the local folder, run `vault-sync doctor`, and ask before installing a recurring pull schedule.

## Direct Skill Fallback

If your environment uses direct skill URLs, point Claude Code at:

```text
https://raw.githubusercontent.com/graphlit/vault-sync/main/skill.md
```

## Manual Fallback

```bash
git clone https://github.com/graphlit/vault-sync
cd vault-sync
install -m 0755 bin/vault-sync ~/.local/bin/vault-sync

vault-sync init <your-vault-repo-url> ~/vaults/<name>
vault-sync schedule install ~/vaults/<name> --every 5m
```

After setup, Claude Code should read `README.md` in the local Vault first, ignore hidden control folders, and inspect Markdown files directly with normal filesystem tools. Treat the Vault as read-only unless you explicitly want local edits.
````

## File: examples/install-codex.md
size: 1555
sha: 5b9689f6d6e242e095352fe96003e62d57a54506
upstream: https://github.com/graphlit/vault-sync/blob/HEAD/examples/install-codex.md

````text
# Codex Install

Use this when a Vault already exists in Git and Codex can access your local filesystem.

## Preferred: Codex Plugin Marketplace

This path requires Codex CLI `0.121.0` or newer.

Add the Vault Sync marketplace:

```bash
codex plugin marketplace add graphlit/vault-sync
```

After install, use the marketplace name `graphlit-vault-sync` for maintenance:

```bash
codex plugin marketplace upgrade graphlit-vault-sync
codex plugin marketplace remove graphlit-vault-sync
```

Then ask Codex:

```text
Use the Vault Sync plugin to set up my Vault from <your-vault-repo-url>.
```

Codex should install `vault-sync`, initialize the local folder, run `vault-sync doctor`, and ask before installing a recurring pull schedule.

## Skill Fallback

If plugin marketplace support is not available in your Codex CLI version, install the skills directly:

```bash
npx skills add graphlit/vault-sync --skill vault-sync-setup
npx skills add graphlit/vault-sync --skill vault-sync-use
```

You can also point Codex at the direct skill entrypoint:

```text
https://raw.githubusercontent.com/graphlit/vault-sync/main/skill.md
```

## Manual Fallback

```bash
git clone https://github.com/graphlit/vault-sync
cd vault-sync
install -m 0755 bin/vault-sync ~/.local/bin/vault-sync

vault-sync init <your-vault-repo-url> ~/vaults/<name>
vault-sync schedule install ~/vaults/<name> --every 5m
```

After setup, Codex should read `README.md` in the local Vault first, ignore hidden control folders, and inspect Markdown files directly with normal filesystem tools.
````

## File: examples/install-openclaw.md
size: 1123
sha: ffb6333cbace7487cf931873c71ef624905cf0b9
upstream: https://github.com/graphlit/vault-sync/blob/HEAD/examples/install-openclaw.md

````text
# OpenClaw Install

Use this when a Vault already exists in Git and OpenClaw can access your local filesystem.

## Preferred: Skill Entry

If OpenClaw can install agent skills from GitHub, install:

```bash
npx skills add graphlit/vault-sync --skill vault-sync-setup
npx skills add graphlit/vault-sync --skill vault-sync-use
```

If it uses direct skill URLs, point it at:

```text
https://raw.githubusercontent.com/graphlit/vault-sync/main/skill.md
```

Then ask OpenClaw:

```text
Set up Vault Sync for my Vault from <your-vault-repo-url>.
```

OpenClaw should install `vault-sync`, initialize the local folder, run `vault-sync doctor`, and ask before installing a recurring pull schedule.

## Manual Fallback

```bash
git clone https://github.com/graphlit/vault-sync
cd vault-sync
install -m 0755 bin/vault-sync ~/.local/bin/vault-sync

vault-sync init <your-vault-repo-url> ~/vaults/<name>
vault-sync schedule install ~/vaults/<name> --every 5m
```

After setup, OpenClaw should read `README.md` in the local Vault first, ignore hidden control folders, and inspect Markdown files directly with normal filesystem tools.
````

## File: LICENSE
size: 1065
sha: f227b16be11256ce4fdcace1744fa284d5362cac
upstream: https://github.com/graphlit/vault-sync/blob/HEAD/LICENSE

```text
MIT License

Copyright (c) 2026 Graphlit

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```

## File: plugins/vault-sync/.codex-plugin/plugin.json
size: 975
sha: 50861295a93fdef55e12fb5329b45699908ac2df
upstream: https://github.com/graphlit/vault-sync/blob/HEAD/plugins/vault-sync/.codex-plugin/plugin.json

```text
{
  "name": "vault-sync",
  "version": "0.1.0",
  "description": "Install and use Vault Sync for filesystem-based Vault consumption.",
  "author": {
    "name": "Graphlit",
    "url": "https://github.com/graphlit/vault-sync"
  },
  "homepage": "https://github.com/graphlit/vault-sync",
  "repository": "https://github.com/graphlit/vault-sync",
  "license": "MIT",
  "keywords": [
    "vault",
    "git",
    "sync",
    "filesystem",
    "agent"
  ],
  "skills": "./skills/",
  "interface": {
    "displayName": "Vault Sync",
    "shortDescription": "Mirror Vault Git repositories locally.",
    "longDescription": "Vault Sync helps Codex install a small Git-based sync script, mirror an existing Vault to local Markdown files, and consume those files with normal filesystem tools.",
    "developerName": "Graphlit",
    "category": "Productivity",
    "defaultPrompt": [
      "Set up Vault Sync for my Vault.",
      "Use my local Vault folder to answer this."
    ]
  }
}
```

## File: plugins/vault-sync/skills/vault-sync-setup/scripts/install-vault-sync.sh
size: 1247
sha: a459fe2ff12882a17b45c69db4347bc591536b1b
upstream: https://github.com/graphlit/vault-sync/blob/HEAD/plugins/vault-sync/skills/vault-sync-setup/scripts/install-vault-sync.sh

```text
#!/usr/bin/env bash
set -euo pipefail

install_dir="${VAULT_SYNC_INSTALL_DIR:-$HOME/.local/bin}"
target="$install_dir/vault-sync"

case "$(uname -s)" in
  Darwin|Linux) ;;
  *)
    echo "Vault Sync currently supports macOS, Linux, and WSL2 Linux environments." >&2
    exit 1
    ;;
esac

if ! command -v git >/dev/null 2>&1; then
  echo "git is required before installing Vault Sync." >&2
  exit 1
fi

mkdir -p "$install_dir"

script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repo_root="${VAULT_SYNC_REPO_ROOT:-$(cd "$script_dir/../../.." && pwd)}"

if [[ -f "$repo_root/bin/vault-sync" ]]; then
  install -m 0755 "$repo_root/bin/vault-sync" "$target"
elif command -v curl >/dev/null 2>&1; then
  curl -fsSL https://raw.githubusercontent.com/graphlit/vault-sync/main/bin/vault-sync -o "$target"
  chmod 0755 "$target"
elif command -v wget >/dev/null 2>&1; then
  wget -qO "$target" https://raw.githubusercontent.com/graphlit/vault-sync/main/bin/vault-sync
  chmod 0755 "$target"
else
  echo "Install from a checkout or install curl/wget to download Vault Sync." >&2
  exit 1
fi

echo "Installed vault-sync at $target"

case ":$PATH:" in
  *":$install_dir:"*) ;;
  *) echo "Note: $install_dir is not on PATH for this shell." >&2 ;;
esac
```

## File: plugins/vault-sync/skills/vault-sync-setup/scripts/setup-vault.sh
size: 1134
sha: e9ae08c0f83f9b1e1e83b7609cbd792d01edbe7a
upstream: https://github.com/graphlit/vault-sync/blob/HEAD/plugins/vault-sync/skills/vault-sync-setup/scripts/setup-vault.sh

```text
#!/usr/bin/env bash
set -euo pipefail

usage() {
  cat <<'USAGE'
Usage: setup-vault.sh <vault-repo-url> [path] [--schedule] [--every 5m]
USAGE
}

if [[ $# -lt 1 ]]; then
  usage >&2
  exit 1
fi

repo_url="$1"
shift

path=""
schedule="false"
every="5m"

while [[ $# -gt 0 ]]; do
  case "$1" in
    --schedule)
      schedule="true"
      shift
      ;;
    --every)
      every="${2:-}"
      if [[ -z "$every" ]]; then
        usage >&2
        exit 1
      fi
      shift 2
      ;;
    -*)
      usage >&2
      exit 1
      ;;
    *)
      if [[ -n "$path" ]]; then
        usage >&2
        exit 1
      fi
      path="$1"
      shift
      ;;
  esac
done

if [[ -z "$path" ]]; then
  name="${repo_url##*/}"
  name="${name%.git}"
  path="$HOME/vaults/$name"
fi

if ! command -v vault-sync >/dev/null 2>&1; then
  script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  bash "$script_dir/install-vault-sync.sh"
fi

vault-sync init "$repo_url" "$path"
vault-sync pull "$path"
vault-sync doctor "$path"

if [[ "$schedule" == "true" ]]; then
  vault-sync schedule install "$path" --every "$every"
fi

echo "Vault ready at $path"
```

## File: skills/vault-sync-setup/scripts/install-vault-sync.sh
size: 1247
sha: a459fe2ff12882a17b45c69db4347bc591536b1b
upstream: https://github.com/graphlit/vault-sync/blob/HEAD/skills/vault-sync-setup/scripts/install-vault-sync.sh

```text
#!/usr/bin/env bash
set -euo pipefail

install_dir="${VAULT_SYNC_INSTALL_DIR:-$HOME/.local/bin}"
target="$install_dir/vault-sync"

case "$(uname -s)" in
  Darwin|Linux) ;;
  *)
    echo "Vault Sync currently supports macOS, Linux, and WSL2 Linux environments." >&2
    exit 1
    ;;
esac

if ! command -v git >/dev/null 2>&1; then
  echo "git is required before installing Vault Sync." >&2
  exit 1
fi

mkdir -p "$install_dir"

script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repo_root="${VAULT_SYNC_REPO_ROOT:-$(cd "$script_dir/../../.." && pwd)}"

if [[ -f "$repo_root/bin/vault-sync" ]]; then
  install -m 0755 "$repo_root/bin/vault-sync" "$target"
elif command -v curl >/dev/null 2>&1; then
  curl -fsSL https://raw.githubusercontent.com/graphlit/vault-sync/main/bin/vault-sync -o "$target"
  chmod 0755 "$target"
elif command -v wget >/dev/null 2>&1; then
  wget -qO "$target" https://raw.githubusercontent.com/graphlit/vault-sync/main/bin/vault-sync
  chmod 0755 "$target"
else
  echo "Install from a checkout or install curl/wget to download Vault Sync." >&2
  exit 1
fi

echo "Installed vault-sync at $target"

case ":$PATH:" in
  *":$install_dir:"*) ;;
  *) echo "Note: $install_dir is not on PATH for this shell." >&2 ;;
esac
```

## File: skills/vault-sync-setup/scripts/setup-vault.sh
size: 1134
sha: e9ae08c0f83f9b1e1e83b7609cbd792d01edbe7a
upstream: https://github.com/graphlit/vault-sync/blob/HEAD/skills/vault-sync-setup/scripts/setup-vault.sh

```text
#!/usr/bin/env bash
set -euo pipefail

usage() {
  cat <<'USAGE'
Usage: setup-vault.sh <vault-repo-url> [path] [--schedule] [--every 5m]
USAGE
}

if [[ $# -lt 1 ]]; then
  usage >&2
  exit 1
fi

repo_url="$1"
shift

path=""
schedule="false"
every="5m"

while [[ $# -gt 0 ]]; do
  case "$1" in
    --schedule)
      schedule="true"
      shift
      ;;
    --every)
      every="${2:-}"
      if [[ -z "$every" ]]; then
        usage >&2
        exit 1
      fi
      shift 2
      ;;
    -*)
      usage >&2
      exit 1
      ;;
    *)
      if [[ -n "$path" ]]; then
        usage >&2
        exit 1
      fi
      path="$1"
      shift
      ;;
  esac
done

if [[ -z "$path" ]]; then
  name="${repo_url##*/}"
  name="${name%.git}"
  path="$HOME/vaults/$name"
fi

if ! command -v vault-sync >/dev/null 2>&1; then
  script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  bash "$script_dir/install-vault-sync.sh"
fi

vault-sync init "$repo_url" "$path"
vault-sync pull "$path"
vault-sync doctor "$path"

if [[ "$schedule" == "true" ]]; then
  vault-sync schedule install "$path" --every "$every"
fi

echo "Vault ready at $path"
```

## File: tests/smoke.sh
size: 1302
sha: 68e1dbea9706f7a0ee9eefde5dbea5f2c971bfba
upstream: https://github.com/graphlit/vault-sync/blob/HEAD/tests/smoke.sh

```text
#!/usr/bin/env bash
set -euo pipefail

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
TMPDIR_ROOT="$(mktemp -d)"
SRC="$TMPDIR_ROOT/source"
VAULT="$TMPDIR_ROOT/vault"
CONFIG="$TMPDIR_ROOT/config"

cleanup() {
  rm -rf "$TMPDIR_ROOT"
}
trap cleanup EXIT

git init -b main "$SRC" >/dev/null
git -C "$SRC" config user.email test@example.com
git -C "$SRC" config user.name "Vault Sync Test"

printf '# Test Vault\n\nhello vault\n' >"$SRC/README.md"
git -C "$SRC" add README.md
git -C "$SRC" commit -m 'initial vault' >/dev/null

VAULT_SYNC_CONFIG_DIR="$CONFIG" bash "$ROOT_DIR/bin/vault-sync" init "$SRC" "$VAULT"
VAULT_SYNC_CONFIG_DIR="$CONFIG" bash "$ROOT_DIR/bin/vault-sync" status "$VAULT" >/dev/null
VAULT_SYNC_CONFIG_DIR="$CONFIG" bash "$ROOT_DIR/bin/vault-sync" status "$VAULT" --json >/dev/null

printf '\nsecond line\n' >>"$SRC/README.md"
git -C "$SRC" add README.md
git -C "$SRC" commit -m 'update vault' >/dev/null

VAULT_SYNC_CONFIG_DIR="$CONFIG" bash "$ROOT_DIR/bin/vault-sync" pull "$VAULT"
VAULT_SYNC_CONFIG_DIR="$CONFIG" bash "$ROOT_DIR/bin/vault-sync" doctor "$VAULT"

if VAULT_SYNC_CONFIG_DIR="$CONFIG" bash "$ROOT_DIR/bin/vault-sync" search "$VAULT" >/dev/null 2>&1; then
  printf 'search unexpectedly succeeded\n' >&2
  exit 1
fi

printf 'vault-sync smoke test passed\n'
```
