Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
9.2 KiB
Capability: obsidian
CRUD headless de vaults y notas de Obsidian, tratadas como Markdown plano con frontmatter YAML y wikilinks [[...]]. El nucleo del grupo manipula los archivos .md directamente en disco (no necesita la app GUI). Un sub-conjunto aparte gestiona la lista de vaults que la app de escritorio Obsidian conoce (su config ~/.config/obsidian/obsidian.json + el URI scheme obsidian://): register_*, list_registered_*, unregister_*, open_obsidian_vault. Scriptable, rapido, con telemetria del registry.
Los vaults de Obsidian del usuario viven en /home/enmanuel/Obsidian/ y estan enlazados como vaults del registry en el project obsidian (projects/obsidian/vaults/). Ver projects/obsidian/project.md.
Funciones
| ID | Firma | Que hace |
|---|---|---|
parse_obsidian_frontmatter_py_obsidian |
parse_obsidian_frontmatter(content: str) -> {"frontmatter": dict, "body": str} |
Pure. Separa el frontmatter YAML (bloque --- inicial) del cuerpo. Si no hay frontmatter valido devuelve {} + el contenido completo. |
extract_obsidian_wikilinks_py_obsidian |
extract_obsidian_wikilinks(body: str) -> list |
Pure. Extrae los targets de los wikilinks [[...]] y embeds ![[...]]. Normaliza [[nota|alias]], [[nota#heading]], [[nota#^block]] -> nota. Dedup preservando orden. |
format_obsidian_note_py_obsidian |
format_obsidian_note(frontmatter: dict, body: str) -> str |
Pure. Inversa de parse: serializa frontmatter (YAML entre ---) + body a una nota .md completa. |
read_obsidian_note_py_obsidian |
read_obsidian_note(path: str) -> dict |
Lee una nota: {path, frontmatter, body, wikilinks, tags}. Compone parse + extract. |
create_obsidian_note_py_obsidian |
create_obsidian_note(vault_dir, rel_path, body="", frontmatter=None, overwrite=False) -> str |
Crea nota nueva (crea dirs padre, añade .md). Error si existe y overwrite=False. |
update_obsidian_note_py_obsidian |
update_obsidian_note(path, body=None, set_frontmatter=None, append=None) -> str |
Edita nota existente: merge de frontmatter, reemplazo de body, o append al final. |
delete_obsidian_note_py_obsidian |
delete_obsidian_note(path: str) -> bool |
Borra una nota (solo archivo, nunca directorio). Error si no existe. |
list_obsidian_notes_py_obsidian |
list_obsidian_notes(vault_dir, subfolder="", tag="") -> list |
Lista paths de notas .md (recursivo). Excluye .obsidian/ y .trash/. Filtro opcional por tag de frontmatter. |
search_obsidian_notes_py_obsidian |
search_obsidian_notes(vault_dir, query, in_body=True, in_frontmatter=True) -> list |
Busca substring (case-insensitive) en las notas. Devuelve [{path, matches:[{line, text}]}]. |
list_obsidian_vaults_py_obsidian |
list_obsidian_vaults(base_dir: str) -> list |
Lista los vaults (subdirs con .obsidian/) bajo base_dir. [{name, path}]. |
create_obsidian_vault_py_obsidian |
create_obsidian_vault(parent_dir, name) -> str |
Crea un vault nuevo: carpeta + .obsidian/app.json minimo. Error si ya existe. |
register_obsidian_vault_py_obsidian |
register_obsidian_vault(vault_path, open=False, config_path="") -> dict |
Da de alta un vault en la app Obsidian (entrada en ~/.config/obsidian/obsidian.json). Idempotente por path, backup .bak, preserva el resto del JSON. NO toca el filesystem del vault. |
list_registered_obsidian_vaults_py_obsidian |
list_registered_obsidian_vaults(config_path="") -> list |
Lista los vaults que la app Obsidian conoce (de obsidian.json), ordenados por path. [{id, path, open, ts}]. Distinto de list_obsidian_vaults (que escanea el filesystem). |
unregister_obsidian_vault_py_obsidian |
unregister_obsidian_vault(vault_ref, config_path="") -> dict |
Quita un vault de la lista de la app Obsidian (por id o por path). NO borra la carpeta del vault. Backup .bak, preserva el resto del JSON. |
open_obsidian_vault_py_obsidian |
open_obsidian_vault(vault, register_if_missing=True, launch=True, config_path="") -> dict |
Abre un vault en la app Obsidian via obsidian://open?vault=<name> (lanza xdg-open). Registra el vault antes si falta. launch=False solo construye el URI. |
slugify_obsidian_name_py_obsidian |
slugify_obsidian_name(name: str) -> str |
Pure. Nombre/titulo -> slug kebab-case estable (translitera acentos, ñ->n). Estabiliza ids de nodo y nombres de archivo. |
extract_obsidian_embeds_py_obsidian |
extract_obsidian_embeds(body: str) -> list |
Pure. Solo los embeds ![[...]] (attachments incrustados), ignorando wikilinks normales. Dedup preservando orden. |
resolve_obsidian_embed_py_obsidian |
resolve_obsidian_embed(vault_dir, embed_name) -> str |
Resuelve un embed ![[foto.jpg]] a su path absoluto real (busca por basename unico en el vault). Cadena vacia si no existe. |
build_obsidian_graph_py_obsidian |
build_obsidian_graph(vault_dir, include_dangling=True) -> {"nodes":[...], "edges":[...]} |
Grafo agregado del vault: cada nota = nodo tipado (id=slug, label, tipo, frontmatter); cada wikilink [[...]] = arista con kind por seccion. Wikilinks rotos -> nodos fantasma dangling. |
render_markdown_table_py_core |
render_markdown_table(rows: list[dict], columns=None, max_rows=0) -> str |
Pure (vive en core). Lista de dicts -> tabla Markdown GFM. Escapa pipes, saltos de linea -> <br>, truncado opcional con pie ... N de M filas. Base del render BD -> nota. |
upsert_sentinel_block_py_core |
upsert_sentinel_block(text, block_id, content, marker="osintdb") -> str |
Pure (vive en core). Inserta o reemplaza un bloque gestionado entre sentinels <!-- marker:begin id=X --> / <!-- marker:end id=X --> dentro del body de una nota. Idempotente; ValueError si el bloque esta corrupto. |
Ejemplo canonico
Componer varias funciones del grupo se hace por heredoc importando del registry (las funciones se importan, no se reescriben):
cd /home/enmanuel/fn_registry
python/.venv/bin/python3 - <<'PYEOF'
import sys
sys.path.insert(0, "python/functions")
from obsidian import (
list_obsidian_vaults, list_obsidian_notes, search_obsidian_notes,
create_obsidian_note, read_obsidian_note, update_obsidian_note, delete_obsidian_note,
)
# 1. Descubrir vaults del usuario
vaults = list_obsidian_vaults("/home/enmanuel/Obsidian")
print("vaults:", [v["name"] for v in vaults])
# 2. Listar y buscar notas en un vault
finanzas = "/home/enmanuel/Obsidian/Finanzas"
print("notas:", len(list_obsidian_notes(finanzas)))
print("hits:", [h["path"] for h in search_obsidian_notes(finanzas, "presupuesto")][:5])
# 3. CRUD de una nota (crear -> leer -> editar -> borrar)
p = create_obsidian_note(finanzas, "inbox/idea_x", body="Primera linea",
frontmatter={"tags": ["inbox"], "created": "2026-06-09"})
note = read_obsidian_note(p)
print("creada:", note["path"], note["frontmatter"], note["wikilinks"])
update_obsidian_note(p, set_frontmatter={"status": "done"}, append="Ver [[Otra Nota]]")
delete_obsidian_note(p)
PYEOF
Para una sola operacion con un id conocido, fn run tambien sirve:
./fn run list_obsidian_vaults /home/enmanuel/Obsidian
./fn run list_obsidian_notes /home/enmanuel/Obsidian/Finanzas
Cuando usar el grupo
- Crear/editar/leer notas de cualquier vault de Obsidian desde un agente o script, sin abrir la app.
- Buscar o listar notas por contenido o tag (ingesta, migracion, reporting sobre el vault).
- Crear vaults nuevos o inventariar los existentes.
Fronteras (que NO cubre)
- El CRUD de notas no habla con la app GUI (no abre notas en la interfaz ni dispara plugins). Si la app esta abierta, escribir en disco puede chocar con sus locks/cache — cerrar la app o refrescar manualmente. La unica interaccion con la app es la gestion de su lista de vaults (
register_*/unregister_*/list_registered_*sobreobsidian.json) yopen_obsidian_vault(lanza el URIobsidian://); estas no editan notas ni renderizan nada. - Single-instance gotcha: Obsidian cachea su
obsidian.jsonen memoria al arrancar. Registrar/desregistrar un vault con la app abierta no se reflejara hasta reiniciarla;open_obsidian_vaultsobre un vault recien registrado puede dar "unable to find a vault" hasta el reinicio. - No resuelve wikilinks a paths automaticamente (devuelve los targets crudos). Resolver
[[nota]]-> archivo real es responsabilidad del caller (busqueda por nombre en el vault). - No renderiza Markdown ni evalua Dataview/templating. Trata las notas como texto + frontmatter.
- El grafo agregado del vault ya lo cubre
build_obsidian_graph_py_obsidian(nodos tipados + aristas conkind+ nodos fantasmadangling). Es la base de la vista grafo (sigma.js) de la apposint_web. Lo que sigue fuera del grupo es el layout visual del grafo (force-directed) — eso vive en el frontend.
Gotchas
- Vaults grandes son caros:
NotasDeObsidianpesa ~554M.list_obsidian_notes/search_obsidian_notesrecorren todo el arbol — filtra porsubfoldercuando puedas. delete_obsidian_noteborra de verdad (no manda a.trash/). Para acciones destructivas masivas, listar primero y confirmar.- El frontmatter
tagspuede venir como lista o como CSV string;read_obsidian_notelo normaliza a lista.