0.9.0 - Added Trilium ETAPI integration.

Lyra can now: Search trilium notes and create new notes. with proper ETAPI auth.
This commit is contained in:
serversdwn
2025-12-29 01:58:20 -05:00
parent 64429b19e6
commit 794baf2a96
14 changed files with 2063 additions and 39 deletions

View File

@@ -0,0 +1,134 @@
"""
Trilium notes executor for searching and creating notes via ETAPI.
This module provides integration with Trilium notes through the ETAPI HTTP API.
"""
import os
import aiohttp
from typing import Dict
TRILIUM_URL = os.getenv("TRILIUM_URL", "http://localhost:8080")
TRILIUM_TOKEN = os.getenv("TRILIUM_ETAPI_TOKEN", "")
async def search_notes(args: Dict) -> Dict:
"""Search Trilium notes via ETAPI.
Args:
args: Dictionary containing:
- query (str): Search query
- limit (int, optional): Maximum notes to return (default: 5, max: 20)
Returns:
dict: Search results containing:
- notes (list): List of notes with noteId, title, content, type
- count (int): Number of notes returned
OR
- error (str): Error message if search failed
"""
query = args.get("query")
limit = args.get("limit", 5)
# Validation
if not query:
return {"error": "No query provided"}
if not TRILIUM_TOKEN:
return {"error": "TRILIUM_ETAPI_TOKEN not configured in environment"}
# Cap limit
limit = min(max(limit, 1), 20)
try:
async with aiohttp.ClientSession() as session:
async with session.get(
f"{TRILIUM_URL}/etapi/notes",
params={"search": query, "limit": limit},
headers={"Authorization": TRILIUM_TOKEN}
) as resp:
if resp.status == 200:
data = await resp.json()
# ETAPI returns {"results": [...]} format
results = data.get("results", [])
return {
"notes": results,
"count": len(results)
}
elif resp.status == 401:
return {"error": "Authentication failed. Check TRILIUM_ETAPI_TOKEN"}
else:
error_text = await resp.text()
return {"error": f"HTTP {resp.status}: {error_text}"}
except aiohttp.ClientConnectorError:
return {"error": f"Cannot connect to Trilium at {TRILIUM_URL}"}
except Exception as e:
return {"error": f"Search failed: {str(e)}"}
async def create_note(args: Dict) -> Dict:
"""Create a note in Trilium via ETAPI.
Args:
args: Dictionary containing:
- title (str): Note title
- content (str): Note content in markdown or HTML
- parent_note_id (str, optional): Parent note ID to nest under
Returns:
dict: Creation result containing:
- noteId (str): ID of created note
- title (str): Title of created note
- success (bool): True if created successfully
OR
- error (str): Error message if creation failed
"""
title = args.get("title")
content = args.get("content")
parent_note_id = args.get("parent_note_id", "root") # Default to root if not specified
# Validation
if not title:
return {"error": "No title provided"}
if not content:
return {"error": "No content provided"}
if not TRILIUM_TOKEN:
return {"error": "TRILIUM_ETAPI_TOKEN not configured in environment"}
# Prepare payload
payload = {
"parentNoteId": parent_note_id, # Always include parentNoteId
"title": title,
"content": content,
"type": "text",
"mime": "text/html"
}
try:
async with aiohttp.ClientSession() as session:
async with session.post(
f"{TRILIUM_URL}/etapi/create-note",
json=payload,
headers={"Authorization": TRILIUM_TOKEN}
) as resp:
if resp.status in [200, 201]:
data = await resp.json()
return {
"noteId": data.get("noteId"),
"title": title,
"success": True
}
elif resp.status == 401:
return {"error": "Authentication failed. Check TRILIUM_ETAPI_TOKEN"}
else:
error_text = await resp.text()
return {"error": f"HTTP {resp.status}: {error_text}"}
except aiohttp.ClientConnectorError:
return {"error": f"Cannot connect to Trilium at {TRILIUM_URL}"}
except Exception as e:
return {"error": f"Note creation failed: {str(e)}"}