From 061147caea7ba845040bfccbd33a35ceefc91b9c Mon Sep 17 00:00:00 2001 From: ginnoir Date: Sat, 6 Jun 2026 03:50:29 -0500 Subject: [PATCH] docs(vault): update skill with correct livesync two-level chunk format --- .claude/skills/vault/SKILL.md | 116 ++++++++++++++++++++-------------- 1 file changed, 70 insertions(+), 46 deletions(-) diff --git a/.claude/skills/vault/SKILL.md b/.claude/skills/vault/SKILL.md index 905750f..26fc033 100644 --- a/.claude/skills/vault/SKILL.md +++ b/.claude/skills/vault/SKILL.md @@ -10,79 +10,103 @@ Obsidian vault backed by CouchDB (obsidian-livesync) at `obsidian.ginnoir.com`. ## Credentials ``` -Endpoint : https://obsidian.ginnoir.com +Endpoint : http://admin:couchdb_obs_p4ss_7x9k@localhost:5984 (via docker exec couchdb) DB : obsidian -User : admin -Password : couchdb_obs_p4ss_7x9k (also in stacks/notes/stack.env) +External : https://obsidian.ginnoir.com (also in stacks/notes/stack.env) ``` -## Document format +## Document format (confirmed from live vault) -Each note is a CouchDB document: +obsidian-livesync uses a **two-level structure**: -| Field | Meaning | -|--------|---------| -| `_id` | Vault-relative path, e.g. `claude/Context.md` or `Projects/homelab.md` | -| `data` | Raw markdown content | -| `type` | `"plain"` for regular notes | -| `ctime`/`mtime` | Unix ms timestamps | -| `size` | Byte length of `data` | +**File doc** — `_id` is the vault-relative path (e.g. `testing.md`, `claude/Context.md`): +```json +{ + "_id": "testing.md", + "path": "testing.md", + "type": "plain", + "children": ["h:1w5waerza6mc9"], + "ctime": 1780735543814, + "mtime": 1780735679684, + "size": 14, + "eden": {} +} +``` -Slashes in `_id` must be URL-encoded as `%2F` in requests. +**Leaf/chunk doc** — `_id` is `h:`, holds the actual content: +```json +{ + "_id": "h:1w5waerza6mc9", + "data": "this is a test", + "type": "leaf" +} +``` -## Read all notes +Content lives in leaf docs. `children[]` lists chunk IDs in order (concatenate for large notes). +Slashes in paths must be URL-encoded as `%2F` in requests. + +## Read all notes (list) ```powershell ssh -o BatchMode=yes ginnoir@valhalla 'docker exec couchdb curl -sf "http://admin:couchdb_obs_p4ss_7x9k@localhost:5984/obsidian/_all_docs?include_docs=true"' ``` -Returns JSON with `rows[].doc.data` = markdown for each note. +File docs: `type == "plain"`, `_id` does not start with `h:` or `obsydian_`. -## Read one note +## Read one note's content ```powershell -# e.g. claude/Context.md -ssh -o BatchMode=yes ginnoir@valhalla 'docker exec couchdb curl -sf "http://admin:couchdb_obs_p4ss_7x9k@localhost:5984/obsidian/claude%2FContext.md"' +# 1. Get file doc — find chunk IDs in children[] +ssh -o BatchMode=yes ginnoir@valhalla 'docker exec couchdb curl -sf "http://admin:couchdb_obs_p4ss_7x9k@localhost:5984/obsidian/testing.md"' + +# 2. Fetch chunk — content is in .data +ssh -o BatchMode=yes ginnoir@valhalla 'docker exec couchdb curl -sf "http://admin:couchdb_obs_p4ss_7x9k@localhost:5984/obsidian/h%3A1w5waerza6mc9"' ``` -## Write / update a note +## Write a new note -Shell escaping inside SSH is treacherous with JSON. The reliable pattern is: +Write the leaf first, then the file doc. Use a fixed chunk ID so you can update it later. -1. Write JSON to `/tmp/note.json` on valhalla -2. `docker cp` it into the container -3. `curl -d @/tmp/note.json` from inside the container - -```powershell -# Step 1 — get current _rev -$rev = (ssh -o BatchMode=yes ginnoir@valhalla 'docker exec couchdb curl -sf "http://admin:couchdb_obs_p4ss_7x9k@localhost:5984/obsidian/claude%2FContext.md"' | python -c "import sys,json; print(json.load(sys.stdin)['_rev'])") - -# Step 2 — write JSON to valhalla /tmp -$json = @{_rev=$rev; data="# My Note`n`nContent"; type="plain"; ctime=0; mtime=0; size=10} | ConvertTo-Json -$json | ssh ginnoir@valhalla "cat > /tmp/note.json" - -# Step 3 — copy to container and PUT +```bash ssh -o BatchMode=yes ginnoir@valhalla ' - docker cp /tmp/note.json couchdb:/tmp/note.json - docker exec couchdb curl -sf -X PUT \ - "http://admin:couchdb_obs_p4ss_7x9k@localhost:5984/obsidian/claude%2FContext.md" \ - -H "Content-Type: application/json" -d @/tmp/note.json + B="http://admin:couchdb_obs_p4ss_7x9k@localhost:5984" + NOW=$(date +%s%3N) + CHUNK="h:claude_ctx_1" + CONTENT="# My Note\n\nContent here." + + docker exec couchdb curl -sf -X PUT "$B/obsidian/$CHUNK" \ + -H "Content-Type: application/json" \ + -d "{\"data\":\"$CONTENT\",\"type\":\"leaf\"}" + + docker exec couchdb curl -sf -X PUT "$B/obsidian/claude%2FContext.md" \ + -H "Content-Type: application/json" \ + -d "{\"path\":\"claude/Context.md\",\"type\":\"plain\",\"children\":[\"$CHUNK\"],\"ctime\":$NOW,\"mtime\":$NOW,\"size\":${#CONTENT},\"eden\":{}}" ' ``` +## Update an existing note + +Fetch `_rev` for both the file doc and chunk doc, then re-PUT both with `_rev` included. + +For complex content (markdown with quotes/newlines), write JSON to `/tmp/` on valhalla, `docker cp` into container, and `curl -d @/tmp/file.json` — avoids shell escaping. + ## Load context at session start ```powershell -ssh -o BatchMode=yes ginnoir@valhalla 'docker exec couchdb curl -sf "http://admin:couchdb_obs_p4ss_7x9k@localhost:5984/obsidian/claude%2FContext.md"' | python3 -c "import sys,json; print(json.load(sys.stdin)['data'])" +# Get all file docs and their chunk content +ssh -o BatchMode=yes ginnoir@valhalla 'docker exec couchdb curl -sf "http://admin:couchdb_obs_p4ss_7x9k@localhost:5984/obsidian/_all_docs?include_docs=true"' | python -c " +import sys, json +d = json.load(sys.stdin) +chunks = {r['id']: r['doc']['data'] for r in d['rows'] if r['id'].startswith('h:')} +for r in d['rows']: + doc = r['doc'] + if doc.get('type') == 'plain' and not doc.get('deleted'): + content = ''.join(chunks.get(c, '') for c in doc.get('children', [])) + print(f'=== {doc[\"_id\"]} ===') + print(content) +" ``` -## Key notes - -| Path | Purpose | -|------|---------| -| `claude/Context.md` | Claude's persistent context: ongoing projects, preferences, session notes | -| `Home.md` | Vault index / root | - ## Notes on valhalla Python -Running `python3` on the valhalla *host* cannot resolve `localhost` (DNS issue in SSH env). Always use `docker exec couchdb curl` (CouchDB's own container) to hit the API — it resolves localhost fine internally. +`python3` on the valhalla *host* cannot resolve `localhost` (DNS issue in SSH env). Always use `docker exec couchdb curl` — the container resolves localhost fine. On the Windows Claude Code host, use `python` (not `python3`).