Files
2026-06-15 01:33:35 +02:00

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_* sobre obsidian.json) y open_obsidian_vault (lanza el URI obsidian://); estas no editan notas ni renderizan nada.
  • Single-instance gotcha: Obsidian cachea su obsidian.json en memoria al arrancar. Registrar/desregistrar un vault con la app abierta no se reflejara hasta reiniciarla; open_obsidian_vault sobre 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 con kind + nodos fantasma dangling). Es la base de la vista grafo (sigma.js) de la app osint_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: NotasDeObsidian pesa ~554M. list_obsidian_notes / search_obsidian_notes recorren todo el arbol — filtra por subfolder cuando puedas.
  • delete_obsidian_note borra de verdad (no manda a .trash/). Para acciones destructivas masivas, listar primero y confirmar.
  • El frontmatter tags puede venir como lista o como CSV string; read_obsidian_note lo normaliza a lista.