--- name: vault description: Read and write notes in ginnoir's self-hosted Obsidian vault (CouchDB obsidian-livesync backend at obsidian.ginnoir.com). Use when loading persistent context for a session, saving new context, writing a note, or querying vault contents. The vault is the canonical persistent-context store across Claude sessions. --- # vault Obsidian vault backed by CouchDB (obsidian-livesync) at `obsidian.ginnoir.com`. ## Credentials ``` Endpoint : http://admin:couchdb_obs_p4ss_7x9k@localhost:5984 (via docker exec couchdb) DB : obsidian External : https://obsidian.ginnoir.com (also in stacks/notes/stack.env) ``` ## Document format (confirmed from live vault) obsidian-livesync uses a **two-level structure**: **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": {} } ``` **Leaf/chunk doc** — `_id` is `h:`, holds the actual content: ```json { "_id": "h:1w5waerza6mc9", "data": "this is a test", "type": "leaf" } ``` 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"' ``` File docs: `type == "plain"`, `_id` does not start with `h:` or `obsydian_`. ## Read one note's content ```powershell # 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 a new note Write the leaf first, then the file doc. Use a fixed chunk ID so you can update it later. ```bash ssh -o BatchMode=yes ginnoir@valhalla ' 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 # 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) " ``` ## Notes on valhalla Python `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`).