Files
fn_registry/docs/capabilities/obsidian.md
T
egutierrez d89da1292d chore: auto-commit (9 archivos)
- docs/capabilities/INDEX.md
- docs/capabilities/obsidian.md
- python/functions/core/render_markdown_table.md
- python/functions/core/render_markdown_table.py
- python/functions/core/render_markdown_table_test.py
- python/functions/core/upsert_sentinel_block.md
- python/functions/core/upsert_sentinel_block.py
- python/functions/core/upsert_sentinel_block_test.py
- python/functions/infra/duckdb_query_readonly.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-13 21:56:56 +02:00

7.3 KiB

Capability: obsidian

CRUD headless de vaults y notas de Obsidian, tratadas como Markdown plano con frontmatter YAML y wikilinks [[...]]. NO depende de la app GUI de Obsidian ni de su URI scheme — manipula los archivos .md directamente en disco. 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.
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)

  • No habla con la app GUI (no usa el URI scheme obsidian://, no abre notas en la interfaz, no dispara plugins). Si la app esta abierta, escribir en disco puede chocar con sus locks/cache — cerrar la app o refrescar manualmente.
  • 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.