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

@@ -2,7 +2,7 @@
from .code_executor import execute_code
from .web_search import search_web
from .trillium import search_notes, create_note
from .trilium import search_notes, create_note
__all__ = [
"execute_code",

View File

@@ -1,7 +1,7 @@
"""
Trillium notes executor for searching and creating notes via ETAPI.
Trilium notes executor for searching and creating notes via ETAPI.
This module provides integration with Trillium notes through the ETAPI HTTP API.
This module provides integration with Trilium notes through the ETAPI HTTP API.
"""
import os
@@ -9,12 +9,12 @@ import aiohttp
from typing import Dict
TRILLIUM_URL = os.getenv("TRILLIUM_URL", "http://localhost:8080")
TRILLIUM_TOKEN = os.getenv("TRILLIUM_ETAPI_TOKEN", "")
TRILIUM_URL = os.getenv("TRILIUM_URL", "http://localhost:8080")
TRILIUM_TOKEN = os.getenv("TRILIUM_ETAPI_TOKEN", "")
async def search_notes(args: Dict) -> Dict:
"""Search Trillium notes via ETAPI.
"""Search Trilium notes via ETAPI.
Args:
args: Dictionary containing:
@@ -35,8 +35,8 @@ async def search_notes(args: Dict) -> Dict:
if not query:
return {"error": "No query provided"}
if not TRILLIUM_TOKEN:
return {"error": "TRILLIUM_ETAPI_TOKEN not configured in environment"}
if not TRILIUM_TOKEN:
return {"error": "TRILIUM_ETAPI_TOKEN not configured in environment"}
# Cap limit
limit = min(max(limit, 1), 20)
@@ -44,30 +44,32 @@ async def search_notes(args: Dict) -> Dict:
try:
async with aiohttp.ClientSession() as session:
async with session.get(
f"{TRILLIUM_URL}/etapi/search-notes",
f"{TRILIUM_URL}/etapi/notes",
params={"search": query, "limit": limit},
headers={"Authorization": TRILLIUM_TOKEN}
headers={"Authorization": TRILIUM_TOKEN}
) as resp:
if resp.status == 200:
data = await resp.json()
# ETAPI returns {"results": [...]} format
results = data.get("results", [])
return {
"notes": data,
"count": len(data)
"notes": results,
"count": len(results)
}
elif resp.status == 401:
return {"error": "Authentication failed. Check TRILLIUM_ETAPI_TOKEN"}
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 Trillium at {TRILLIUM_URL}"}
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 Trillium via ETAPI.
"""Create a note in Trilium via ETAPI.
Args:
args: Dictionary containing:
@@ -85,7 +87,7 @@ async def create_note(args: Dict) -> Dict:
"""
title = args.get("title")
content = args.get("content")
parent_note_id = args.get("parent_note_id")
parent_note_id = args.get("parent_note_id", "root") # Default to root if not specified
# Validation
if not title:
@@ -94,26 +96,24 @@ async def create_note(args: Dict) -> Dict:
if not content:
return {"error": "No content provided"}
if not TRILLIUM_TOKEN:
return {"error": "TRILLIUM_ETAPI_TOKEN not configured in environment"}
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"
}
if parent_note_id:
payload["parentNoteId"] = parent_note_id
try:
async with aiohttp.ClientSession() as session:
async with session.post(
f"{TRILLIUM_URL}/etapi/create-note",
f"{TRILIUM_URL}/etapi/create-note",
json=payload,
headers={"Authorization": TRILLIUM_TOKEN}
headers={"Authorization": TRILIUM_TOKEN}
) as resp:
if resp.status in [200, 201]:
data = await resp.json()
@@ -123,12 +123,12 @@ async def create_note(args: Dict) -> Dict:
"success": True
}
elif resp.status == 401:
return {"error": "Authentication failed. Check TRILLIUM_ETAPI_TOKEN"}
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 Trillium at {TRILLIUM_URL}"}
return {"error": f"Cannot connect to Trilium at {TRILIUM_URL}"}
except Exception as e:
return {"error": f"Note creation failed: {str(e)}"}