Files
homelabstack/.claude/skills/vault/SKILL.md
T

3.9 KiB

name, description
name description
vault 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):

{
  "_id": "testing.md",
  "path": "testing.md",
  "type": "plain",
  "children": ["h:1w5waerza6mc9"],
  "ctime": 1780735543814,
  "mtime": 1780735679684,
  "size": 14,
  "eden": {}
}

Leaf/chunk doc_id is h:<short-hash>, holds the actual content:

{
  "_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)

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

# 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.

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

# 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).