ebk 0.4.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ebk/__init__.py +35 -0
- ebk/ai/__init__.py +23 -0
- ebk/ai/knowledge_graph.py +450 -0
- ebk/ai/llm_providers/__init__.py +26 -0
- ebk/ai/llm_providers/anthropic.py +209 -0
- ebk/ai/llm_providers/base.py +295 -0
- ebk/ai/llm_providers/gemini.py +285 -0
- ebk/ai/llm_providers/ollama.py +294 -0
- ebk/ai/metadata_enrichment.py +394 -0
- ebk/ai/question_generator.py +328 -0
- ebk/ai/reading_companion.py +224 -0
- ebk/ai/semantic_search.py +433 -0
- ebk/ai/text_extractor.py +393 -0
- ebk/calibre_import.py +66 -0
- ebk/cli.py +6433 -0
- ebk/config.py +230 -0
- ebk/db/__init__.py +37 -0
- ebk/db/migrations.py +507 -0
- ebk/db/models.py +725 -0
- ebk/db/session.py +144 -0
- ebk/decorators.py +1 -0
- ebk/exports/__init__.py +0 -0
- ebk/exports/base_exporter.py +218 -0
- ebk/exports/echo_export.py +279 -0
- ebk/exports/html_library.py +1743 -0
- ebk/exports/html_utils.py +87 -0
- ebk/exports/hugo.py +59 -0
- ebk/exports/jinja_export.py +286 -0
- ebk/exports/multi_facet_export.py +159 -0
- ebk/exports/opds_export.py +232 -0
- ebk/exports/symlink_dag.py +479 -0
- ebk/exports/zip.py +25 -0
- ebk/extract_metadata.py +341 -0
- ebk/ident.py +89 -0
- ebk/library_db.py +1440 -0
- ebk/opds.py +748 -0
- ebk/plugins/__init__.py +42 -0
- ebk/plugins/base.py +502 -0
- ebk/plugins/hooks.py +442 -0
- ebk/plugins/registry.py +499 -0
- ebk/repl/__init__.py +9 -0
- ebk/repl/find.py +126 -0
- ebk/repl/grep.py +173 -0
- ebk/repl/shell.py +1677 -0
- ebk/repl/text_utils.py +320 -0
- ebk/search_parser.py +413 -0
- ebk/server.py +3608 -0
- ebk/services/__init__.py +28 -0
- ebk/services/annotation_extraction.py +351 -0
- ebk/services/annotation_service.py +380 -0
- ebk/services/export_service.py +577 -0
- ebk/services/import_service.py +447 -0
- ebk/services/personal_metadata_service.py +347 -0
- ebk/services/queue_service.py +253 -0
- ebk/services/tag_service.py +281 -0
- ebk/services/text_extraction.py +317 -0
- ebk/services/view_service.py +12 -0
- ebk/similarity/__init__.py +77 -0
- ebk/similarity/base.py +154 -0
- ebk/similarity/core.py +471 -0
- ebk/similarity/extractors.py +168 -0
- ebk/similarity/metrics.py +376 -0
- ebk/skills/SKILL.md +182 -0
- ebk/skills/__init__.py +1 -0
- ebk/vfs/__init__.py +101 -0
- ebk/vfs/base.py +298 -0
- ebk/vfs/library_vfs.py +122 -0
- ebk/vfs/nodes/__init__.py +54 -0
- ebk/vfs/nodes/authors.py +196 -0
- ebk/vfs/nodes/books.py +480 -0
- ebk/vfs/nodes/files.py +155 -0
- ebk/vfs/nodes/metadata.py +385 -0
- ebk/vfs/nodes/root.py +100 -0
- ebk/vfs/nodes/similar.py +165 -0
- ebk/vfs/nodes/subjects.py +184 -0
- ebk/vfs/nodes/tags.py +371 -0
- ebk/vfs/resolver.py +228 -0
- ebk/vfs_router.py +275 -0
- ebk/views/__init__.py +32 -0
- ebk/views/dsl.py +668 -0
- ebk/views/service.py +619 -0
- ebk-0.4.4.dist-info/METADATA +755 -0
- ebk-0.4.4.dist-info/RECORD +87 -0
- ebk-0.4.4.dist-info/WHEEL +5 -0
- ebk-0.4.4.dist-info/entry_points.txt +2 -0
- ebk-0.4.4.dist-info/licenses/LICENSE +21 -0
- ebk-0.4.4.dist-info/top_level.txt +1 -0
ebk/plugins/hooks.py
ADDED
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Hook system for EBK plugins.
|
|
3
|
+
|
|
4
|
+
This module provides a hook system that allows plugins and user code
|
|
5
|
+
to register callbacks for various events in the EBK lifecycle.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Callable, Any, List, Dict, Optional
|
|
11
|
+
from collections import defaultdict
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class HookRegistry:
|
|
17
|
+
"""Registry for managing hook callbacks."""
|
|
18
|
+
|
|
19
|
+
def __init__(self):
|
|
20
|
+
self._hooks: Dict[str, List[Callable]] = defaultdict(list)
|
|
21
|
+
self._async_hooks: Dict[str, List[Callable]] = defaultdict(list)
|
|
22
|
+
self._hook_priorities: Dict[str, Dict[Callable, int]] = defaultdict(dict)
|
|
23
|
+
self._hook_descriptions: Dict[str, str] = {}
|
|
24
|
+
|
|
25
|
+
def register_hook(self,
|
|
26
|
+
event: str,
|
|
27
|
+
callback: Callable,
|
|
28
|
+
priority: int = 0,
|
|
29
|
+
description: Optional[str] = None) -> None:
|
|
30
|
+
"""
|
|
31
|
+
Register a hook callback.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
event: Event name to hook into
|
|
35
|
+
callback: Callback function
|
|
36
|
+
priority: Priority (higher runs first)
|
|
37
|
+
description: Optional description of what this hook does
|
|
38
|
+
"""
|
|
39
|
+
if asyncio.iscoroutinefunction(callback):
|
|
40
|
+
self._async_hooks[event].append(callback)
|
|
41
|
+
else:
|
|
42
|
+
self._hooks[event].append(callback)
|
|
43
|
+
|
|
44
|
+
self._hook_priorities[event][callback] = priority
|
|
45
|
+
|
|
46
|
+
# Sort by priority
|
|
47
|
+
if event in self._hooks:
|
|
48
|
+
self._hooks[event].sort(
|
|
49
|
+
key=lambda cb: self._hook_priorities[event].get(cb, 0),
|
|
50
|
+
reverse=True
|
|
51
|
+
)
|
|
52
|
+
if event in self._async_hooks:
|
|
53
|
+
self._async_hooks[event].sort(
|
|
54
|
+
key=lambda cb: self._hook_priorities[event].get(cb, 0),
|
|
55
|
+
reverse=True
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
if description:
|
|
59
|
+
hook_id = f"{event}:{callback.__name__}"
|
|
60
|
+
self._hook_descriptions[hook_id] = description
|
|
61
|
+
|
|
62
|
+
logger.debug(f"Registered hook for {event}: {callback.__name__} (priority: {priority})")
|
|
63
|
+
|
|
64
|
+
def unregister_hook(self, event: str, callback: Callable) -> bool:
|
|
65
|
+
"""
|
|
66
|
+
Unregister a hook callback.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
event: Event name
|
|
70
|
+
callback: Callback to remove
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
True if callback was removed
|
|
74
|
+
"""
|
|
75
|
+
removed = False
|
|
76
|
+
|
|
77
|
+
if callback in self._hooks.get(event, []):
|
|
78
|
+
self._hooks[event].remove(callback)
|
|
79
|
+
removed = True
|
|
80
|
+
|
|
81
|
+
if callback in self._async_hooks.get(event, []):
|
|
82
|
+
self._async_hooks[event].remove(callback)
|
|
83
|
+
removed = True
|
|
84
|
+
|
|
85
|
+
if removed:
|
|
86
|
+
self._hook_priorities[event].pop(callback, None)
|
|
87
|
+
logger.debug(f"Unregistered hook for {event}: {callback.__name__}")
|
|
88
|
+
|
|
89
|
+
return removed
|
|
90
|
+
|
|
91
|
+
def trigger(self, event: str, *args, **kwargs) -> List[Any]:
|
|
92
|
+
"""
|
|
93
|
+
Trigger all callbacks for an event (synchronous).
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
event: Event name
|
|
97
|
+
*args: Positional arguments for callbacks
|
|
98
|
+
**kwargs: Keyword arguments for callbacks
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
List of results from callbacks
|
|
102
|
+
"""
|
|
103
|
+
results = []
|
|
104
|
+
|
|
105
|
+
# Run synchronous hooks
|
|
106
|
+
for callback in self._hooks.get(event, []):
|
|
107
|
+
try:
|
|
108
|
+
result = callback(*args, **kwargs)
|
|
109
|
+
if result is not None:
|
|
110
|
+
results.append(result)
|
|
111
|
+
except Exception as e:
|
|
112
|
+
logger.error(f"Hook {callback.__name__} failed for event {event}: {e}")
|
|
113
|
+
|
|
114
|
+
# Handle async hooks in sync context
|
|
115
|
+
if event in self._async_hooks:
|
|
116
|
+
logger.warning(f"Async hooks registered for {event} but triggered synchronously")
|
|
117
|
+
|
|
118
|
+
return results
|
|
119
|
+
|
|
120
|
+
async def trigger_async(self, event: str, *args, **kwargs) -> List[Any]:
|
|
121
|
+
"""
|
|
122
|
+
Trigger all callbacks for an event (asynchronous).
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
event: Event name
|
|
126
|
+
*args: Positional arguments for callbacks
|
|
127
|
+
**kwargs: Keyword arguments for callbacks
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
List of results from callbacks
|
|
131
|
+
"""
|
|
132
|
+
results = []
|
|
133
|
+
|
|
134
|
+
# Run synchronous hooks
|
|
135
|
+
for callback in self._hooks.get(event, []):
|
|
136
|
+
try:
|
|
137
|
+
result = callback(*args, **kwargs)
|
|
138
|
+
if result is not None:
|
|
139
|
+
results.append(result)
|
|
140
|
+
except Exception as e:
|
|
141
|
+
logger.error(f"Hook {callback.__name__} failed for event {event}: {e}")
|
|
142
|
+
|
|
143
|
+
# Run async hooks
|
|
144
|
+
tasks = []
|
|
145
|
+
for callback in self._async_hooks.get(event, []):
|
|
146
|
+
tasks.append(self._run_async_hook(callback, event, args, kwargs))
|
|
147
|
+
|
|
148
|
+
if tasks:
|
|
149
|
+
async_results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
150
|
+
for result in async_results:
|
|
151
|
+
if isinstance(result, Exception):
|
|
152
|
+
logger.error(f"Async hook failed for event {event}: {result}")
|
|
153
|
+
elif result is not None:
|
|
154
|
+
results.append(result)
|
|
155
|
+
|
|
156
|
+
return results
|
|
157
|
+
|
|
158
|
+
async def _run_async_hook(self, callback: Callable, event: str, args, kwargs) -> Any:
|
|
159
|
+
"""
|
|
160
|
+
Run a single async hook with error handling.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
callback: Async callback function
|
|
164
|
+
event: Event name
|
|
165
|
+
args: Positional arguments
|
|
166
|
+
kwargs: Keyword arguments
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
Result from callback or None
|
|
170
|
+
"""
|
|
171
|
+
try:
|
|
172
|
+
return await callback(*args, **kwargs)
|
|
173
|
+
except Exception as e:
|
|
174
|
+
logger.error(f"Async hook {callback.__name__} failed for event {event}: {e}")
|
|
175
|
+
raise
|
|
176
|
+
|
|
177
|
+
def trigger_filter(self, event: str, value: Any, *args, **kwargs) -> Any:
|
|
178
|
+
"""
|
|
179
|
+
Trigger filter hooks that can modify a value.
|
|
180
|
+
|
|
181
|
+
Each hook receives the current value and can return a modified version.
|
|
182
|
+
If a hook returns None, the value is unchanged.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
event: Event name
|
|
186
|
+
value: Initial value to filter
|
|
187
|
+
*args: Additional positional arguments for callbacks
|
|
188
|
+
**kwargs: Additional keyword arguments for callbacks
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Final filtered value
|
|
192
|
+
"""
|
|
193
|
+
current_value = value
|
|
194
|
+
|
|
195
|
+
for callback in self._hooks.get(event, []):
|
|
196
|
+
try:
|
|
197
|
+
result = callback(current_value, *args, **kwargs)
|
|
198
|
+
if result is not None:
|
|
199
|
+
current_value = result
|
|
200
|
+
except Exception as e:
|
|
201
|
+
logger.error(f"Filter hook {callback.__name__} failed for event {event}: {e}")
|
|
202
|
+
|
|
203
|
+
return current_value
|
|
204
|
+
|
|
205
|
+
async def trigger_filter_async(self, event: str, value: Any, *args, **kwargs) -> Any:
|
|
206
|
+
"""
|
|
207
|
+
Trigger async filter hooks that can modify a value.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
event: Event name
|
|
211
|
+
value: Initial value to filter
|
|
212
|
+
*args: Additional positional arguments for callbacks
|
|
213
|
+
**kwargs: Additional keyword arguments for callbacks
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
Final filtered value
|
|
217
|
+
"""
|
|
218
|
+
current_value = value
|
|
219
|
+
|
|
220
|
+
# Run synchronous hooks first
|
|
221
|
+
current_value = self.trigger_filter(event, current_value, *args, **kwargs)
|
|
222
|
+
|
|
223
|
+
# Run async hooks
|
|
224
|
+
for callback in self._async_hooks.get(event, []):
|
|
225
|
+
try:
|
|
226
|
+
result = await callback(current_value, *args, **kwargs)
|
|
227
|
+
if result is not None:
|
|
228
|
+
current_value = result
|
|
229
|
+
except Exception as e:
|
|
230
|
+
logger.error(f"Async filter hook {callback.__name__} failed for event {event}: {e}")
|
|
231
|
+
|
|
232
|
+
return current_value
|
|
233
|
+
|
|
234
|
+
def has_hooks(self, event: str) -> bool:
|
|
235
|
+
"""
|
|
236
|
+
Check if an event has any hooks registered.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
event: Event name
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
True if hooks are registered
|
|
243
|
+
"""
|
|
244
|
+
return bool(self._hooks.get(event) or self._async_hooks.get(event))
|
|
245
|
+
|
|
246
|
+
def list_hooks(self) -> Dict[str, List[str]]:
|
|
247
|
+
"""
|
|
248
|
+
List all registered hooks.
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
Dictionary mapping events to callback names
|
|
252
|
+
"""
|
|
253
|
+
result = {}
|
|
254
|
+
|
|
255
|
+
for event, callbacks in self._hooks.items():
|
|
256
|
+
if event not in result:
|
|
257
|
+
result[event] = []
|
|
258
|
+
result[event].extend([cb.__name__ for cb in callbacks])
|
|
259
|
+
|
|
260
|
+
for event, callbacks in self._async_hooks.items():
|
|
261
|
+
if event not in result:
|
|
262
|
+
result[event] = []
|
|
263
|
+
result[event].extend([f"{cb.__name__} (async)" for cb in callbacks])
|
|
264
|
+
|
|
265
|
+
return result
|
|
266
|
+
|
|
267
|
+
def clear_hooks(self, event: Optional[str] = None) -> None:
|
|
268
|
+
"""
|
|
269
|
+
Clear hooks for an event or all events.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
event: Event name to clear, or None to clear all
|
|
273
|
+
"""
|
|
274
|
+
if event:
|
|
275
|
+
self._hooks.pop(event, None)
|
|
276
|
+
self._async_hooks.pop(event, None)
|
|
277
|
+
self._hook_priorities.pop(event, None)
|
|
278
|
+
else:
|
|
279
|
+
self._hooks.clear()
|
|
280
|
+
self._async_hooks.clear()
|
|
281
|
+
self._hook_priorities.clear()
|
|
282
|
+
self._hook_descriptions.clear()
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
# Global hook registry
|
|
286
|
+
hooks = HookRegistry()
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def hook(event: str, priority: int = 0, description: Optional[str] = None):
|
|
290
|
+
"""
|
|
291
|
+
Decorator for registering hook callbacks.
|
|
292
|
+
|
|
293
|
+
Usage:
|
|
294
|
+
@hook("entry.added")
|
|
295
|
+
def on_entry_added(entry, library):
|
|
296
|
+
print(f"Entry added: {entry['title']}")
|
|
297
|
+
|
|
298
|
+
@hook("before_export", priority=10)
|
|
299
|
+
async def validate_before_export(entries, format):
|
|
300
|
+
# Async hook
|
|
301
|
+
await validate_entries(entries)
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
event: Event name to hook into
|
|
305
|
+
priority: Priority (higher runs first)
|
|
306
|
+
description: Optional description
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
Decorator function
|
|
310
|
+
"""
|
|
311
|
+
def decorator(func: Callable) -> Callable:
|
|
312
|
+
hooks.register_hook(event, func, priority, description)
|
|
313
|
+
return func
|
|
314
|
+
return decorator
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def trigger_hook(event: str, *args, **kwargs) -> List[Any]:
|
|
318
|
+
"""
|
|
319
|
+
Trigger all callbacks for an event.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
event: Event name
|
|
323
|
+
*args: Positional arguments for callbacks
|
|
324
|
+
**kwargs: Keyword arguments for callbacks
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
List of results from callbacks
|
|
328
|
+
"""
|
|
329
|
+
return hooks.trigger(event, *args, **kwargs)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
async def trigger_hook_async(event: str, *args, **kwargs) -> List[Any]:
|
|
333
|
+
"""
|
|
334
|
+
Trigger all callbacks for an event (async).
|
|
335
|
+
|
|
336
|
+
Args:
|
|
337
|
+
event: Event name
|
|
338
|
+
*args: Positional arguments for callbacks
|
|
339
|
+
**kwargs: Keyword arguments for callbacks
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
List of results from callbacks
|
|
343
|
+
"""
|
|
344
|
+
return await hooks.trigger_async(event, *args, **kwargs)
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def filter_value(event: str, value: Any, *args, **kwargs) -> Any:
|
|
348
|
+
"""
|
|
349
|
+
Apply filter hooks to modify a value.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
event: Event name
|
|
353
|
+
value: Initial value
|
|
354
|
+
*args: Additional arguments for callbacks
|
|
355
|
+
**kwargs: Additional keyword arguments for callbacks
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
Filtered value
|
|
359
|
+
"""
|
|
360
|
+
return hooks.trigger_filter(event, value, *args, **kwargs)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
async def filter_value_async(event: str, value: Any, *args, **kwargs) -> Any:
|
|
364
|
+
"""
|
|
365
|
+
Apply async filter hooks to modify a value.
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
event: Event name
|
|
369
|
+
value: Initial value
|
|
370
|
+
*args: Additional arguments for callbacks
|
|
371
|
+
**kwargs: Additional keyword arguments for callbacks
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
Filtered value
|
|
375
|
+
"""
|
|
376
|
+
return await hooks.trigger_filter_async(event, value, *args, **kwargs)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
# Predefined events that EBK will trigger
|
|
380
|
+
EVENTS = {
|
|
381
|
+
# Library events
|
|
382
|
+
'library.opened': 'Library has been opened',
|
|
383
|
+
'library.closed': 'Library has been closed',
|
|
384
|
+
'library.saved': 'Library has been saved',
|
|
385
|
+
|
|
386
|
+
# Entry events
|
|
387
|
+
'entry.added': 'Entry added to library',
|
|
388
|
+
'entry.updated': 'Entry updated',
|
|
389
|
+
'entry.deleted': 'Entry deleted from library',
|
|
390
|
+
'entry.before_add': 'Before entry is added (can cancel)',
|
|
391
|
+
'entry.before_update': 'Before entry is updated (can cancel)',
|
|
392
|
+
'entry.before_delete': 'Before entry is deleted (can cancel)',
|
|
393
|
+
|
|
394
|
+
# Metadata events
|
|
395
|
+
'metadata.extracted': 'Metadata extracted from source',
|
|
396
|
+
'metadata.enriched': 'Metadata enriched from external source',
|
|
397
|
+
'metadata.validated': 'Metadata validated',
|
|
398
|
+
|
|
399
|
+
# Tag events
|
|
400
|
+
'tags.suggested': 'Tags suggested for entry',
|
|
401
|
+
'tags.added': 'Tags added to entry',
|
|
402
|
+
'tags.removed': 'Tags removed from entry',
|
|
403
|
+
|
|
404
|
+
# Import/Export events
|
|
405
|
+
'import.started': 'Import operation started',
|
|
406
|
+
'import.progress': 'Import operation progress',
|
|
407
|
+
'import.completed': 'Import operation completed',
|
|
408
|
+
'import.failed': 'Import operation failed',
|
|
409
|
+
'export.started': 'Export operation started',
|
|
410
|
+
'export.progress': 'Export operation progress',
|
|
411
|
+
'export.completed': 'Export operation completed',
|
|
412
|
+
'export.failed': 'Export operation failed',
|
|
413
|
+
|
|
414
|
+
# Search events
|
|
415
|
+
'search.started': 'Search operation started',
|
|
416
|
+
'search.completed': 'Search operation completed',
|
|
417
|
+
'search.results_filtered': 'Search results filtered',
|
|
418
|
+
|
|
419
|
+
# Plugin events
|
|
420
|
+
'plugin.registered': 'Plugin registered',
|
|
421
|
+
'plugin.unregistered': 'Plugin unregistered',
|
|
422
|
+
'plugin.enabled': 'Plugin enabled',
|
|
423
|
+
'plugin.disabled': 'Plugin disabled',
|
|
424
|
+
'plugin.configured': 'Plugin configured',
|
|
425
|
+
|
|
426
|
+
# Filter events (value can be modified)
|
|
427
|
+
'filter.entry_data': 'Filter entry data before save',
|
|
428
|
+
'filter.search_query': 'Filter search query before execution',
|
|
429
|
+
'filter.export_entries': 'Filter entries before export',
|
|
430
|
+
'filter.import_entry': 'Filter entry during import',
|
|
431
|
+
'filter.suggested_tags': 'Filter suggested tags',
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def list_available_events() -> Dict[str, str]:
|
|
436
|
+
"""
|
|
437
|
+
List all available events with descriptions.
|
|
438
|
+
|
|
439
|
+
Returns:
|
|
440
|
+
Dictionary mapping event names to descriptions
|
|
441
|
+
"""
|
|
442
|
+
return EVENTS.copy()
|