""" 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)}"}