pomera-ai-commander 1.1.1 → 1.2.2

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.
Files changed (213) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +105 -680
  3. package/bin/pomera-ai-commander.js +62 -62
  4. package/core/__init__.py +65 -65
  5. package/core/app_context.py +482 -482
  6. package/core/async_text_processor.py +421 -421
  7. package/core/backup_manager.py +655 -655
  8. package/core/backup_recovery_manager.py +1199 -1033
  9. package/core/content_hash_cache.py +508 -508
  10. package/core/context_menu.py +313 -313
  11. package/core/data_directory.py +549 -0
  12. package/core/data_validator.py +1066 -1066
  13. package/core/database_connection_manager.py +744 -744
  14. package/core/database_curl_settings_manager.py +608 -608
  15. package/core/database_promera_ai_settings_manager.py +446 -446
  16. package/core/database_schema.py +411 -411
  17. package/core/database_schema_manager.py +395 -395
  18. package/core/database_settings_manager.py +1507 -1507
  19. package/core/database_settings_manager_interface.py +456 -456
  20. package/core/dialog_manager.py +734 -734
  21. package/core/diff_utils.py +239 -0
  22. package/core/efficient_line_numbers.py +540 -510
  23. package/core/error_handler.py +746 -746
  24. package/core/error_service.py +431 -431
  25. package/core/event_consolidator.py +511 -511
  26. package/core/mcp/__init__.py +43 -43
  27. package/core/mcp/find_replace_diff.py +334 -0
  28. package/core/mcp/protocol.py +288 -288
  29. package/core/mcp/schema.py +251 -251
  30. package/core/mcp/server_stdio.py +299 -299
  31. package/core/mcp/tool_registry.py +2699 -2345
  32. package/core/memento.py +275 -0
  33. package/core/memory_efficient_text_widget.py +711 -711
  34. package/core/migration_manager.py +914 -914
  35. package/core/migration_test_suite.py +1085 -1085
  36. package/core/migration_validator.py +1143 -1143
  37. package/core/optimized_find_replace.py +714 -714
  38. package/core/optimized_pattern_engine.py +424 -424
  39. package/core/optimized_search_highlighter.py +552 -552
  40. package/core/performance_monitor.py +674 -674
  41. package/core/persistence_manager.py +712 -712
  42. package/core/progressive_stats_calculator.py +632 -632
  43. package/core/regex_pattern_cache.py +529 -529
  44. package/core/regex_pattern_library.py +350 -350
  45. package/core/search_operation_manager.py +434 -434
  46. package/core/settings_defaults_registry.py +1087 -1087
  47. package/core/settings_integrity_validator.py +1111 -1111
  48. package/core/settings_serializer.py +557 -557
  49. package/core/settings_validator.py +1823 -1823
  50. package/core/smart_stats_calculator.py +709 -709
  51. package/core/statistics_update_manager.py +619 -619
  52. package/core/stats_config_manager.py +858 -858
  53. package/core/streaming_text_handler.py +723 -723
  54. package/core/task_scheduler.py +596 -596
  55. package/core/update_pattern_library.py +168 -168
  56. package/core/visibility_monitor.py +596 -596
  57. package/core/widget_cache.py +498 -498
  58. package/mcp.json +51 -61
  59. package/migrate_data.py +127 -0
  60. package/package.json +64 -57
  61. package/pomera.py +7883 -7482
  62. package/pomera_mcp_server.py +183 -144
  63. package/requirements.txt +33 -0
  64. package/scripts/Dockerfile.alpine +43 -0
  65. package/scripts/Dockerfile.gui-test +54 -0
  66. package/scripts/Dockerfile.linux +43 -0
  67. package/scripts/Dockerfile.test-linux +80 -0
  68. package/scripts/Dockerfile.ubuntu +39 -0
  69. package/scripts/README.md +53 -0
  70. package/scripts/build-all.bat +113 -0
  71. package/scripts/build-docker.bat +53 -0
  72. package/scripts/build-docker.sh +55 -0
  73. package/scripts/build-optimized.bat +101 -0
  74. package/scripts/build.sh +78 -0
  75. package/scripts/docker-compose.test.yml +27 -0
  76. package/scripts/docker-compose.yml +32 -0
  77. package/scripts/postinstall.js +62 -0
  78. package/scripts/requirements-minimal.txt +33 -0
  79. package/scripts/test-linux-simple.bat +28 -0
  80. package/scripts/validate-release-workflow.py +450 -0
  81. package/tools/__init__.py +4 -4
  82. package/tools/ai_tools.py +2891 -2891
  83. package/tools/ascii_art_generator.py +352 -352
  84. package/tools/base64_tools.py +183 -183
  85. package/tools/base_tool.py +511 -511
  86. package/tools/case_tool.py +308 -308
  87. package/tools/column_tools.py +395 -395
  88. package/tools/cron_tool.py +884 -884
  89. package/tools/curl_history.py +600 -600
  90. package/tools/curl_processor.py +1207 -1207
  91. package/tools/curl_settings.py +502 -502
  92. package/tools/curl_tool.py +5467 -5467
  93. package/tools/diff_viewer.py +1817 -1072
  94. package/tools/email_extraction_tool.py +248 -248
  95. package/tools/email_header_analyzer.py +425 -425
  96. package/tools/extraction_tools.py +250 -250
  97. package/tools/find_replace.py +2289 -1750
  98. package/tools/folder_file_reporter.py +1463 -1463
  99. package/tools/folder_file_reporter_adapter.py +480 -480
  100. package/tools/generator_tools.py +1216 -1216
  101. package/tools/hash_generator.py +255 -255
  102. package/tools/html_tool.py +656 -656
  103. package/tools/jsonxml_tool.py +729 -729
  104. package/tools/line_tools.py +419 -419
  105. package/tools/markdown_tools.py +561 -561
  106. package/tools/mcp_widget.py +1417 -1417
  107. package/tools/notes_widget.py +978 -973
  108. package/tools/number_base_converter.py +372 -372
  109. package/tools/regex_extractor.py +571 -571
  110. package/tools/slug_generator.py +310 -310
  111. package/tools/sorter_tools.py +458 -458
  112. package/tools/string_escape_tool.py +392 -392
  113. package/tools/text_statistics_tool.py +365 -365
  114. package/tools/text_wrapper.py +430 -430
  115. package/tools/timestamp_converter.py +421 -421
  116. package/tools/tool_loader.py +710 -710
  117. package/tools/translator_tools.py +522 -522
  118. package/tools/url_link_extractor.py +261 -261
  119. package/tools/url_parser.py +204 -204
  120. package/tools/whitespace_tools.py +355 -355
  121. package/tools/word_frequency_counter.py +146 -146
  122. package/core/__pycache__/__init__.cpython-313.pyc +0 -0
  123. package/core/__pycache__/app_context.cpython-313.pyc +0 -0
  124. package/core/__pycache__/async_text_processor.cpython-313.pyc +0 -0
  125. package/core/__pycache__/backup_manager.cpython-313.pyc +0 -0
  126. package/core/__pycache__/backup_recovery_manager.cpython-313.pyc +0 -0
  127. package/core/__pycache__/content_hash_cache.cpython-313.pyc +0 -0
  128. package/core/__pycache__/context_menu.cpython-313.pyc +0 -0
  129. package/core/__pycache__/data_validator.cpython-313.pyc +0 -0
  130. package/core/__pycache__/database_connection_manager.cpython-313.pyc +0 -0
  131. package/core/__pycache__/database_curl_settings_manager.cpython-313.pyc +0 -0
  132. package/core/__pycache__/database_promera_ai_settings_manager.cpython-313.pyc +0 -0
  133. package/core/__pycache__/database_schema.cpython-313.pyc +0 -0
  134. package/core/__pycache__/database_schema_manager.cpython-313.pyc +0 -0
  135. package/core/__pycache__/database_settings_manager.cpython-313.pyc +0 -0
  136. package/core/__pycache__/database_settings_manager_interface.cpython-313.pyc +0 -0
  137. package/core/__pycache__/dialog_manager.cpython-313.pyc +0 -0
  138. package/core/__pycache__/efficient_line_numbers.cpython-313.pyc +0 -0
  139. package/core/__pycache__/error_handler.cpython-313.pyc +0 -0
  140. package/core/__pycache__/error_service.cpython-313.pyc +0 -0
  141. package/core/__pycache__/event_consolidator.cpython-313.pyc +0 -0
  142. package/core/__pycache__/memory_efficient_text_widget.cpython-313.pyc +0 -0
  143. package/core/__pycache__/migration_manager.cpython-313.pyc +0 -0
  144. package/core/__pycache__/migration_test_suite.cpython-313.pyc +0 -0
  145. package/core/__pycache__/migration_validator.cpython-313.pyc +0 -0
  146. package/core/__pycache__/optimized_find_replace.cpython-313.pyc +0 -0
  147. package/core/__pycache__/optimized_pattern_engine.cpython-313.pyc +0 -0
  148. package/core/__pycache__/optimized_search_highlighter.cpython-313.pyc +0 -0
  149. package/core/__pycache__/performance_monitor.cpython-313.pyc +0 -0
  150. package/core/__pycache__/persistence_manager.cpython-313.pyc +0 -0
  151. package/core/__pycache__/progressive_stats_calculator.cpython-313.pyc +0 -0
  152. package/core/__pycache__/regex_pattern_cache.cpython-313.pyc +0 -0
  153. package/core/__pycache__/regex_pattern_library.cpython-313.pyc +0 -0
  154. package/core/__pycache__/search_operation_manager.cpython-313.pyc +0 -0
  155. package/core/__pycache__/settings_defaults_registry.cpython-313.pyc +0 -0
  156. package/core/__pycache__/settings_integrity_validator.cpython-313.pyc +0 -0
  157. package/core/__pycache__/settings_serializer.cpython-313.pyc +0 -0
  158. package/core/__pycache__/settings_validator.cpython-313.pyc +0 -0
  159. package/core/__pycache__/smart_stats_calculator.cpython-313.pyc +0 -0
  160. package/core/__pycache__/statistics_update_manager.cpython-313.pyc +0 -0
  161. package/core/__pycache__/stats_config_manager.cpython-313.pyc +0 -0
  162. package/core/__pycache__/streaming_text_handler.cpython-313.pyc +0 -0
  163. package/core/__pycache__/task_scheduler.cpython-313.pyc +0 -0
  164. package/core/__pycache__/visibility_monitor.cpython-313.pyc +0 -0
  165. package/core/__pycache__/widget_cache.cpython-313.pyc +0 -0
  166. package/core/mcp/__pycache__/__init__.cpython-313.pyc +0 -0
  167. package/core/mcp/__pycache__/protocol.cpython-313.pyc +0 -0
  168. package/core/mcp/__pycache__/schema.cpython-313.pyc +0 -0
  169. package/core/mcp/__pycache__/server_stdio.cpython-313.pyc +0 -0
  170. package/core/mcp/__pycache__/tool_registry.cpython-313.pyc +0 -0
  171. package/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  172. package/tools/__pycache__/ai_tools.cpython-313.pyc +0 -0
  173. package/tools/__pycache__/ascii_art_generator.cpython-313.pyc +0 -0
  174. package/tools/__pycache__/base64_tools.cpython-313.pyc +0 -0
  175. package/tools/__pycache__/base_tool.cpython-313.pyc +0 -0
  176. package/tools/__pycache__/case_tool.cpython-313.pyc +0 -0
  177. package/tools/__pycache__/column_tools.cpython-313.pyc +0 -0
  178. package/tools/__pycache__/cron_tool.cpython-313.pyc +0 -0
  179. package/tools/__pycache__/curl_history.cpython-313.pyc +0 -0
  180. package/tools/__pycache__/curl_processor.cpython-313.pyc +0 -0
  181. package/tools/__pycache__/curl_settings.cpython-313.pyc +0 -0
  182. package/tools/__pycache__/curl_tool.cpython-313.pyc +0 -0
  183. package/tools/__pycache__/diff_viewer.cpython-313.pyc +0 -0
  184. package/tools/__pycache__/email_extraction_tool.cpython-313.pyc +0 -0
  185. package/tools/__pycache__/email_header_analyzer.cpython-313.pyc +0 -0
  186. package/tools/__pycache__/extraction_tools.cpython-313.pyc +0 -0
  187. package/tools/__pycache__/find_replace.cpython-313.pyc +0 -0
  188. package/tools/__pycache__/folder_file_reporter.cpython-313.pyc +0 -0
  189. package/tools/__pycache__/folder_file_reporter_adapter.cpython-313.pyc +0 -0
  190. package/tools/__pycache__/generator_tools.cpython-313.pyc +0 -0
  191. package/tools/__pycache__/hash_generator.cpython-313.pyc +0 -0
  192. package/tools/__pycache__/html_tool.cpython-313.pyc +0 -0
  193. package/tools/__pycache__/huggingface_helper.cpython-313.pyc +0 -0
  194. package/tools/__pycache__/jsonxml_tool.cpython-313.pyc +0 -0
  195. package/tools/__pycache__/line_tools.cpython-313.pyc +0 -0
  196. package/tools/__pycache__/list_comparator.cpython-313.pyc +0 -0
  197. package/tools/__pycache__/markdown_tools.cpython-313.pyc +0 -0
  198. package/tools/__pycache__/mcp_widget.cpython-313.pyc +0 -0
  199. package/tools/__pycache__/notes_widget.cpython-313.pyc +0 -0
  200. package/tools/__pycache__/number_base_converter.cpython-313.pyc +0 -0
  201. package/tools/__pycache__/regex_extractor.cpython-313.pyc +0 -0
  202. package/tools/__pycache__/slug_generator.cpython-313.pyc +0 -0
  203. package/tools/__pycache__/sorter_tools.cpython-313.pyc +0 -0
  204. package/tools/__pycache__/string_escape_tool.cpython-313.pyc +0 -0
  205. package/tools/__pycache__/text_statistics_tool.cpython-313.pyc +0 -0
  206. package/tools/__pycache__/text_wrapper.cpython-313.pyc +0 -0
  207. package/tools/__pycache__/timestamp_converter.cpython-313.pyc +0 -0
  208. package/tools/__pycache__/tool_loader.cpython-313.pyc +0 -0
  209. package/tools/__pycache__/translator_tools.cpython-313.pyc +0 -0
  210. package/tools/__pycache__/url_link_extractor.cpython-313.pyc +0 -0
  211. package/tools/__pycache__/url_parser.cpython-313.pyc +0 -0
  212. package/tools/__pycache__/whitespace_tools.cpython-313.pyc +0 -0
  213. package/tools/__pycache__/word_frequency_counter.cpython-313.pyc +0 -0
@@ -1,710 +1,710 @@
1
- """
2
- Tool Loader - Centralized tool registration and lazy loading.
3
-
4
- This module replaces the 38+ try/except import blocks in pomera.py with a cleaner
5
- registry-based approach that supports lazy loading.
6
-
7
- Author: Pomera AI Commander Team
8
- """
9
-
10
- import importlib
11
- import logging
12
- from typing import Dict, Any, Optional, Callable, Type, List, Tuple
13
- from dataclasses import dataclass, field
14
- from enum import Enum
15
-
16
-
17
- logger = logging.getLogger(__name__)
18
-
19
-
20
- class ToolCategory(Enum):
21
- """Tool categories for organization."""
22
- CORE = "Core Tools"
23
- AI = "AI Tools"
24
- EXTRACTION = "Extraction Tools"
25
- CONVERSION = "Conversion Tools"
26
- TEXT_MANIPULATION = "Text Manipulation"
27
- GENERATORS = "Generators"
28
- ANALYSIS = "Analysis Tools"
29
- UTILITY = "Utility Tools"
30
- MCP = "MCP Tools"
31
-
32
-
33
- @dataclass
34
- class ToolSpec:
35
- """
36
- Specification for a loadable tool.
37
-
38
- Attributes:
39
- name: Display name of the tool (used in UI)
40
- module_path: Python module path (e.g., "tools.case_tool")
41
- class_name: Name of the class to import (e.g., "CaseTool")
42
- category: Tool category for organization
43
- widget_class: Optional separate widget class name
44
- dependencies: Optional list of required pip packages
45
- description: Tool description for UI/help
46
- is_widget: True if the class is a full widget (not just a tool class)
47
- available_flag: Legacy flag name for backwards compatibility
48
- """
49
- name: str
50
- module_path: str
51
- class_name: str
52
- category: ToolCategory = ToolCategory.UTILITY
53
- widget_class: Optional[str] = None
54
- dependencies: List[str] = field(default_factory=list)
55
- description: str = ""
56
- is_widget: bool = False
57
- available_flag: str = "" # e.g., "CASE_TOOL_MODULE_AVAILABLE"
58
-
59
-
60
- # Complete tool specifications registry
61
- TOOL_SPECS: Dict[str, ToolSpec] = {
62
- # Core Tools
63
- "Case Tool": ToolSpec(
64
- name="Case Tool",
65
- module_path="tools.case_tool",
66
- class_name="CaseTool",
67
- category=ToolCategory.CORE,
68
- description="Transform text case (uppercase, lowercase, title case, etc.)",
69
- available_flag="CASE_TOOL_MODULE_AVAILABLE"
70
- ),
71
- "Find & Replace": ToolSpec(
72
- name="Find & Replace",
73
- module_path="tools.find_replace",
74
- class_name="FindReplaceWidget",
75
- category=ToolCategory.CORE,
76
- is_widget=True,
77
- description="Find and replace text with regex support",
78
- available_flag="FIND_REPLACE_MODULE_AVAILABLE"
79
- ),
80
- "Diff Viewer": ToolSpec(
81
- name="Diff Viewer",
82
- module_path="tools.diff_viewer",
83
- class_name="DiffViewerWidget",
84
- widget_class="DiffViewerSettingsWidget",
85
- category=ToolCategory.CORE,
86
- is_widget=True,
87
- description="Compare and view differences between texts",
88
- available_flag="DIFF_VIEWER_MODULE_AVAILABLE"
89
- ),
90
-
91
- # AI Tools
92
- "AI Tools": ToolSpec(
93
- name="AI Tools",
94
- module_path="tools.ai_tools",
95
- class_name="AIToolsWidget",
96
- category=ToolCategory.AI,
97
- is_widget=True,
98
- description="AI-powered text processing with multiple providers",
99
- available_flag="AI_TOOLS_AVAILABLE"
100
- ),
101
-
102
- # Extraction Tools
103
- "Email Extraction": ToolSpec(
104
- name="Email Extraction",
105
- module_path="tools.email_extraction_tool",
106
- class_name="EmailExtractionTool",
107
- category=ToolCategory.EXTRACTION,
108
- description="Extract email addresses from text",
109
- available_flag="EMAIL_EXTRACTION_MODULE_AVAILABLE"
110
- ),
111
- "Email Header Analyzer": ToolSpec(
112
- name="Email Header Analyzer",
113
- module_path="tools.email_header_analyzer",
114
- class_name="EmailHeaderAnalyzer",
115
- category=ToolCategory.EXTRACTION,
116
- description="Analyze email headers for routing and authentication info",
117
- available_flag="EMAIL_HEADER_ANALYZER_MODULE_AVAILABLE"
118
- ),
119
- "URL Link Extractor": ToolSpec(
120
- name="URL Link Extractor",
121
- module_path="tools.url_link_extractor",
122
- class_name="URLLinkExtractor",
123
- category=ToolCategory.EXTRACTION,
124
- description="Extract URLs and links from text",
125
- available_flag="URL_LINK_EXTRACTOR_MODULE_AVAILABLE"
126
- ),
127
- "Regex Extractor": ToolSpec(
128
- name="Regex Extractor",
129
- module_path="tools.regex_extractor",
130
- class_name="RegexExtractor",
131
- category=ToolCategory.EXTRACTION,
132
- description="Extract text patterns using regular expressions",
133
- available_flag="REGEX_EXTRACTOR_MODULE_AVAILABLE"
134
- ),
135
- "URL Parser": ToolSpec(
136
- name="URL Parser",
137
- module_path="tools.url_parser",
138
- class_name="URLParser",
139
- category=ToolCategory.EXTRACTION,
140
- description="Parse and analyze URL components",
141
- available_flag="URL_PARSER_MODULE_AVAILABLE"
142
- ),
143
- "HTML Tool": ToolSpec(
144
- name="HTML Tool",
145
- module_path="tools.html_tool",
146
- class_name="HTMLExtractionTool",
147
- category=ToolCategory.EXTRACTION,
148
- description="Extract content from HTML",
149
- available_flag="HTML_EXTRACTION_TOOL_MODULE_AVAILABLE"
150
- ),
151
- "Extraction Tools": ToolSpec(
152
- name="Extraction Tools",
153
- module_path="tools.extraction_tools",
154
- class_name="ExtractionTools",
155
- category=ToolCategory.EXTRACTION,
156
- description="General purpose extraction utilities",
157
- available_flag="EXTRACTION_TOOLS_MODULE_AVAILABLE"
158
- ),
159
-
160
- # Conversion Tools
161
- "Base64 Tools": ToolSpec(
162
- name="Base64 Tools",
163
- module_path="tools.base64_tools",
164
- class_name="Base64Tools",
165
- widget_class="Base64ToolsWidget",
166
- category=ToolCategory.CONVERSION,
167
- description="Encode and decode Base64",
168
- available_flag="BASE64_TOOLS_MODULE_AVAILABLE"
169
- ),
170
- "JSON/XML Tool": ToolSpec(
171
- name="JSON/XML Tool",
172
- module_path="tools.jsonxml_tool",
173
- class_name="JSONXMLTool",
174
- category=ToolCategory.CONVERSION,
175
- description="Convert between JSON and XML formats",
176
- available_flag="JSONXML_TOOL_MODULE_AVAILABLE"
177
- ),
178
- "Hash Generator": ToolSpec(
179
- name="Hash Generator",
180
- module_path="tools.hash_generator",
181
- class_name="HashGenerator",
182
- category=ToolCategory.CONVERSION,
183
- description="Generate MD5, SHA1, SHA256 and other hashes",
184
- available_flag="HASH_GENERATOR_MODULE_AVAILABLE"
185
- ),
186
- "Number Base Converter": ToolSpec(
187
- name="Number Base Converter",
188
- module_path="tools.number_base_converter",
189
- class_name="NumberBaseConverter",
190
- category=ToolCategory.CONVERSION,
191
- description="Convert numbers between binary, octal, decimal, hex",
192
- available_flag="NUMBER_BASE_CONVERTER_MODULE_AVAILABLE"
193
- ),
194
- "Timestamp Converter": ToolSpec(
195
- name="Timestamp Converter",
196
- module_path="tools.timestamp_converter",
197
- class_name="TimestampConverter",
198
- category=ToolCategory.CONVERSION,
199
- description="Convert between timestamp formats",
200
- available_flag="TIMESTAMP_CONVERTER_MODULE_AVAILABLE"
201
- ),
202
- "String Escape Tool": ToolSpec(
203
- name="String Escape Tool",
204
- module_path="tools.string_escape_tool",
205
- class_name="StringEscapeTool",
206
- category=ToolCategory.CONVERSION,
207
- description="Escape/unescape strings for various formats",
208
- available_flag="STRING_ESCAPE_TOOL_MODULE_AVAILABLE"
209
- ),
210
-
211
- # Text Manipulation Tools
212
- "Sorter Tools": ToolSpec(
213
- name="Sorter Tools",
214
- module_path="tools.sorter_tools",
215
- class_name="SorterTools",
216
- category=ToolCategory.TEXT_MANIPULATION,
217
- description="Sort lines alphabetically or numerically",
218
- available_flag="SORTER_TOOLS_MODULE_AVAILABLE"
219
- ),
220
- "Line Tools": ToolSpec(
221
- name="Line Tools",
222
- module_path="tools.line_tools",
223
- class_name="LineTools",
224
- category=ToolCategory.TEXT_MANIPULATION,
225
- description="Line manipulation (remove duplicates, number lines, etc.)",
226
- available_flag="LINE_TOOLS_MODULE_AVAILABLE"
227
- ),
228
- "Whitespace Tools": ToolSpec(
229
- name="Whitespace Tools",
230
- module_path="tools.whitespace_tools",
231
- class_name="WhitespaceTools",
232
- category=ToolCategory.TEXT_MANIPULATION,
233
- description="Trim, normalize whitespace and line endings",
234
- available_flag="WHITESPACE_TOOLS_MODULE_AVAILABLE"
235
- ),
236
- "Column Tools": ToolSpec(
237
- name="Column Tools",
238
- module_path="tools.column_tools",
239
- class_name="ColumnTools",
240
- category=ToolCategory.TEXT_MANIPULATION,
241
- description="CSV and column manipulation",
242
- available_flag="COLUMN_TOOLS_MODULE_AVAILABLE"
243
- ),
244
- "Text Wrapper": ToolSpec(
245
- name="Text Wrapper",
246
- module_path="tools.text_wrapper",
247
- class_name="TextWrapper",
248
- category=ToolCategory.TEXT_MANIPULATION,
249
- description="Wrap text to specified width",
250
- available_flag="TEXT_WRAPPER_MODULE_AVAILABLE"
251
- ),
252
- "Markdown Tools": ToolSpec(
253
- name="Markdown Tools",
254
- module_path="tools.markdown_tools",
255
- class_name="MarkdownTools",
256
- category=ToolCategory.TEXT_MANIPULATION,
257
- description="Process and extract from Markdown",
258
- available_flag="MARKDOWN_TOOLS_MODULE_AVAILABLE"
259
- ),
260
- "Slug Generator": ToolSpec(
261
- name="Slug Generator",
262
- module_path="tools.slug_generator",
263
- class_name="SlugGenerator",
264
- category=ToolCategory.TEXT_MANIPULATION,
265
- description="Generate URL-friendly slugs",
266
- available_flag="SLUG_GENERATOR_MODULE_AVAILABLE"
267
- ),
268
- "Translator Tools": ToolSpec(
269
- name="Translator Tools",
270
- module_path="tools.translator_tools",
271
- class_name="TranslatorTools",
272
- category=ToolCategory.TEXT_MANIPULATION,
273
- description="Translate to/from Morse code and binary",
274
- available_flag="TRANSLATOR_TOOLS_MODULE_AVAILABLE"
275
- ),
276
-
277
- # Generator Tools
278
- "Generator Tools": ToolSpec(
279
- name="Generator Tools",
280
- module_path="tools.generator_tools",
281
- class_name="GeneratorTools",
282
- widget_class="GeneratorToolsWidget",
283
- category=ToolCategory.GENERATORS,
284
- description="Generate passwords, UUIDs, Lorem Ipsum",
285
- available_flag="GENERATOR_TOOLS_MODULE_AVAILABLE"
286
- ),
287
- "ASCII Art Generator": ToolSpec(
288
- name="ASCII Art Generator",
289
- module_path="tools.ascii_art_generator",
290
- class_name="ASCIIArtGenerator",
291
- category=ToolCategory.GENERATORS,
292
- description="Generate ASCII art from text",
293
- available_flag="ASCII_ART_GENERATOR_MODULE_AVAILABLE"
294
- ),
295
-
296
- # Analysis Tools
297
- "Word Frequency Counter": ToolSpec(
298
- name="Word Frequency Counter",
299
- module_path="tools.word_frequency_counter",
300
- class_name="WordFrequencyCounter",
301
- category=ToolCategory.ANALYSIS,
302
- description="Count word frequencies in text",
303
- available_flag="WORD_FREQUENCY_COUNTER_MODULE_AVAILABLE"
304
- ),
305
- "Text Statistics": ToolSpec(
306
- name="Text Statistics",
307
- module_path="tools.text_statistics_tool",
308
- class_name="TextStatistics",
309
- category=ToolCategory.ANALYSIS,
310
- description="Calculate text statistics (chars, words, lines)",
311
- available_flag="TEXT_STATISTICS_MODULE_AVAILABLE"
312
- ),
313
- "Cron Tool": ToolSpec(
314
- name="Cron Tool",
315
- module_path="tools.cron_tool",
316
- class_name="CronTool",
317
- category=ToolCategory.ANALYSIS,
318
- description="Parse and explain cron expressions",
319
- available_flag="CRON_TOOL_MODULE_AVAILABLE"
320
- ),
321
-
322
- # Utility Tools
323
- "cURL Tool": ToolSpec(
324
- name="cURL Tool",
325
- module_path="tools.curl_tool",
326
- class_name="CurlToolWidget",
327
- category=ToolCategory.UTILITY,
328
- is_widget=True,
329
- description="Make HTTP requests",
330
- available_flag="CURL_TOOL_MODULE_AVAILABLE"
331
- ),
332
- "List Comparator": ToolSpec(
333
- name="List Comparator",
334
- module_path="tools.list_comparator",
335
- class_name="DiffApp",
336
- category=ToolCategory.UTILITY,
337
- description="Compare two lists and find differences",
338
- available_flag="LIST_COMPARATOR_MODULE_AVAILABLE"
339
- ),
340
- "Notes Widget": ToolSpec(
341
- name="Notes Widget",
342
- module_path="tools.notes_widget",
343
- class_name="NotesWidget",
344
- category=ToolCategory.UTILITY,
345
- is_widget=True,
346
- description="Save and manage notes",
347
- available_flag="NOTES_WIDGET_MODULE_AVAILABLE"
348
- ),
349
- "Folder File Reporter": ToolSpec(
350
- name="Folder File Reporter",
351
- module_path="tools.folder_file_reporter_adapter",
352
- class_name="FolderFileReporterAdapter",
353
- category=ToolCategory.UTILITY,
354
- description="Generate reports of folder contents",
355
- available_flag="FOLDER_FILE_REPORTER_MODULE_AVAILABLE"
356
- ),
357
-
358
- # MCP Tools
359
- "MCP Manager": ToolSpec(
360
- name="MCP Manager",
361
- module_path="tools.mcp_widget",
362
- class_name="MCPManager",
363
- category=ToolCategory.MCP,
364
- is_widget=True,
365
- description="Model Context Protocol server management",
366
- available_flag="MCP_WIDGET_MODULE_AVAILABLE"
367
- ),
368
- }
369
-
370
-
371
- class ToolLoader:
372
- """
373
- Centralized tool loading with lazy initialization.
374
-
375
- Benefits:
376
- - Single place to manage all tool imports
377
- - Lazy loading - tools only loaded when first accessed
378
- - Clean availability checking
379
- - Reduces startup time
380
- - Caches loaded modules and classes
381
-
382
- Usage:
383
- loader = get_tool_loader()
384
-
385
- # Check if tool is available
386
- if loader.is_available("Case Tool"):
387
- # Get the tool class
388
- CaseTool = loader.get_tool_class("Case Tool")
389
- tool = CaseTool()
390
-
391
- # Or create instance directly
392
- tool = loader.create_instance("Case Tool")
393
-
394
- # Get all available tools
395
- available = loader.get_available_tools()
396
- """
397
-
398
- def __init__(self, tool_specs: Optional[Dict[str, ToolSpec]] = None):
399
- """
400
- Initialize the tool loader.
401
-
402
- Args:
403
- tool_specs: Optional custom tool specifications (uses TOOL_SPECS if None)
404
- """
405
- self._specs = tool_specs or TOOL_SPECS.copy()
406
- self._loaded_modules: Dict[str, Any] = {}
407
- self._loaded_classes: Dict[str, Type] = {}
408
- self._availability_cache: Dict[str, bool] = {}
409
- self._load_errors: Dict[str, str] = {}
410
- self._widget_classes: Dict[str, Type] = {}
411
-
412
- def register_tool(self, spec: ToolSpec) -> None:
413
- """
414
- Register a new tool specification.
415
-
416
- Args:
417
- spec: Tool specification to register
418
- """
419
- self._specs[spec.name] = spec
420
- # Clear caches for this tool
421
- self._availability_cache.pop(spec.name, None)
422
- self._loaded_classes.pop(spec.name, None)
423
- self._load_errors.pop(spec.name, None)
424
- logger.debug(f"Registered tool: {spec.name}")
425
-
426
- def unregister_tool(self, name: str) -> bool:
427
- """
428
- Unregister a tool.
429
-
430
- Args:
431
- name: Tool name to unregister
432
-
433
- Returns:
434
- True if tool was found and removed
435
- """
436
- if name in self._specs:
437
- del self._specs[name]
438
- self._availability_cache.pop(name, None)
439
- self._loaded_classes.pop(name, None)
440
- self._load_errors.pop(name, None)
441
- return True
442
- return False
443
-
444
- def is_available(self, tool_name: str) -> bool:
445
- """
446
- Check if a tool is available (can be imported).
447
-
448
- Args:
449
- tool_name: Name of the tool
450
-
451
- Returns:
452
- True if tool module can be imported
453
- """
454
- if tool_name in self._availability_cache:
455
- return self._availability_cache[tool_name]
456
-
457
- if tool_name not in self._specs:
458
- self._availability_cache[tool_name] = False
459
- return False
460
-
461
- spec = self._specs[tool_name]
462
- try:
463
- importlib.import_module(spec.module_path)
464
- self._availability_cache[tool_name] = True
465
- return True
466
- except ImportError as e:
467
- self._availability_cache[tool_name] = False
468
- self._load_errors[tool_name] = str(e)
469
- logger.debug(f"Tool '{tool_name}' not available: {e}")
470
- return False
471
-
472
- def get_tool_class(self, tool_name: str) -> Optional[Type]:
473
- """
474
- Get the tool class (lazy loaded).
475
-
476
- Args:
477
- tool_name: Name of the tool
478
-
479
- Returns:
480
- The tool class, or None if not available
481
- """
482
- if tool_name in self._loaded_classes:
483
- return self._loaded_classes[tool_name]
484
-
485
- if not self.is_available(tool_name):
486
- return None
487
-
488
- spec = self._specs[tool_name]
489
- try:
490
- module = self._get_module(spec.module_path)
491
- tool_class = getattr(module, spec.class_name)
492
- self._loaded_classes[tool_name] = tool_class
493
- logger.info(f"Loaded tool class: {tool_name}")
494
- return tool_class
495
- except (ImportError, AttributeError) as e:
496
- self._load_errors[tool_name] = str(e)
497
- logger.error(f"Failed to load tool class '{tool_name}': {e}")
498
- return None
499
-
500
- def get_widget_class(self, tool_name: str) -> Optional[Type]:
501
- """
502
- Get the widget class for a tool (if it has a separate one).
503
-
504
- Args:
505
- tool_name: Name of the tool
506
-
507
- Returns:
508
- The widget class, or None if not available
509
- """
510
- if tool_name in self._widget_classes:
511
- return self._widget_classes[tool_name]
512
-
513
- if tool_name not in self._specs:
514
- return None
515
-
516
- spec = self._specs[tool_name]
517
-
518
- # If no separate widget class, return the main class
519
- if not spec.widget_class:
520
- return self.get_tool_class(tool_name)
521
-
522
- try:
523
- module = self._get_module(spec.module_path)
524
- widget_class = getattr(module, spec.widget_class)
525
- self._widget_classes[tool_name] = widget_class
526
- return widget_class
527
- except (ImportError, AttributeError) as e:
528
- logger.error(f"Failed to load widget class for '{tool_name}': {e}")
529
- return None
530
-
531
- def create_instance(self, tool_name: str, *args, **kwargs) -> Optional[Any]:
532
- """
533
- Create an instance of a tool.
534
-
535
- Args:
536
- tool_name: Name of the tool
537
- *args, **kwargs: Arguments to pass to the constructor
538
-
539
- Returns:
540
- Tool instance, or None if not available
541
- """
542
- tool_class = self.get_tool_class(tool_name)
543
- if tool_class is None:
544
- return None
545
-
546
- try:
547
- return tool_class(*args, **kwargs)
548
- except Exception as e:
549
- logger.error(f"Failed to instantiate tool '{tool_name}': {e}")
550
- return None
551
-
552
- def _get_module(self, module_path: str) -> Any:
553
- """Get a module (cached)."""
554
- if module_path not in self._loaded_modules:
555
- self._loaded_modules[module_path] = importlib.import_module(module_path)
556
- return self._loaded_modules[module_path]
557
-
558
- def get_available_tools(self) -> List[str]:
559
- """
560
- Get list of all available tool names.
561
-
562
- Returns:
563
- List of tool names that can be loaded
564
- """
565
- return [name for name in self._specs.keys() if self.is_available(name)]
566
-
567
- def get_all_tool_names(self) -> List[str]:
568
- """
569
- Get list of all registered tool names.
570
-
571
- Returns:
572
- List of all tool names (whether available or not)
573
- """
574
- return list(self._specs.keys())
575
-
576
- def get_tools_by_category(self, category: ToolCategory) -> List[str]:
577
- """
578
- Get tools in a specific category.
579
-
580
- Args:
581
- category: Tool category
582
-
583
- Returns:
584
- List of tool names in that category
585
- """
586
- return [
587
- name for name, spec in self._specs.items()
588
- if spec.category == category and self.is_available(name)
589
- ]
590
-
591
- def get_tool_spec(self, tool_name: str) -> Optional[ToolSpec]:
592
- """
593
- Get the specification for a tool.
594
-
595
- Args:
596
- tool_name: Name of the tool
597
-
598
- Returns:
599
- ToolSpec or None if not found
600
- """
601
- return self._specs.get(tool_name)
602
-
603
- def get_load_error(self, tool_name: str) -> Optional[str]:
604
- """
605
- Get the error message if a tool failed to load.
606
-
607
- Args:
608
- tool_name: Name of the tool
609
-
610
- Returns:
611
- Error message, or None if no error
612
- """
613
- return self._load_errors.get(tool_name)
614
-
615
- def get_availability_report(self) -> Dict[str, Dict[str, Any]]:
616
- """
617
- Get a report of all tools and their availability.
618
-
619
- Returns:
620
- Dictionary with tool availability information
621
- """
622
- report = {}
623
- for name, spec in self._specs.items():
624
- available = self.is_available(name)
625
- report[name] = {
626
- 'available': available,
627
- 'category': spec.category.value,
628
- 'module': spec.module_path,
629
- 'class': spec.class_name,
630
- 'is_widget': spec.is_widget,
631
- 'error': self._load_errors.get(name) if not available else None
632
- }
633
- return report
634
-
635
- def preload_tools(self, tool_names: Optional[List[str]] = None) -> Dict[str, bool]:
636
- """
637
- Preload specified tools (or all if none specified).
638
-
639
- Args:
640
- tool_names: List of tool names to preload, or None for all
641
-
642
- Returns:
643
- Dictionary of tool names to load success status
644
- """
645
- names = tool_names or list(self._specs.keys())
646
- results = {}
647
- for name in names:
648
- results[name] = self.get_tool_class(name) is not None
649
- return results
650
-
651
- def clear_cache(self) -> None:
652
- """Clear all cached modules and classes."""
653
- self._loaded_modules.clear()
654
- self._loaded_classes.clear()
655
- self._availability_cache.clear()
656
- self._load_errors.clear()
657
- self._widget_classes.clear()
658
- logger.debug("Tool loader cache cleared")
659
-
660
- def get_legacy_flags(self) -> Dict[str, bool]:
661
- """
662
- Get legacy availability flags for backwards compatibility.
663
-
664
- Returns:
665
- Dictionary of flag names to boolean values
666
- """
667
- flags = {}
668
- for name, spec in self._specs.items():
669
- if spec.available_flag:
670
- flags[spec.available_flag] = self.is_available(name)
671
- return flags
672
-
673
-
674
- # Global instance
675
- _tool_loader: Optional[ToolLoader] = None
676
-
677
-
678
- def get_tool_loader() -> ToolLoader:
679
- """
680
- Get the global tool loader instance.
681
-
682
- Returns:
683
- Global ToolLoader instance
684
- """
685
- global _tool_loader
686
- if _tool_loader is None:
687
- _tool_loader = ToolLoader()
688
- return _tool_loader
689
-
690
-
691
- def init_tool_loader(tool_specs: Optional[Dict[str, ToolSpec]] = None) -> ToolLoader:
692
- """
693
- Initialize the global tool loader.
694
-
695
- Args:
696
- tool_specs: Optional custom tool specifications
697
-
698
- Returns:
699
- Initialized ToolLoader
700
- """
701
- global _tool_loader
702
- _tool_loader = ToolLoader(tool_specs)
703
- return _tool_loader
704
-
705
-
706
- def reset_tool_loader() -> None:
707
- """Reset the global tool loader."""
708
- global _tool_loader
709
- _tool_loader = None
710
-
1
+ """
2
+ Tool Loader - Centralized tool registration and lazy loading.
3
+
4
+ This module replaces the 38+ try/except import blocks in pomera.py with a cleaner
5
+ registry-based approach that supports lazy loading.
6
+
7
+ Author: Pomera AI Commander Team
8
+ """
9
+
10
+ import importlib
11
+ import logging
12
+ from typing import Dict, Any, Optional, Callable, Type, List, Tuple
13
+ from dataclasses import dataclass, field
14
+ from enum import Enum
15
+
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class ToolCategory(Enum):
21
+ """Tool categories for organization."""
22
+ CORE = "Core Tools"
23
+ AI = "AI Tools"
24
+ EXTRACTION = "Extraction Tools"
25
+ CONVERSION = "Conversion Tools"
26
+ TEXT_MANIPULATION = "Text Manipulation"
27
+ GENERATORS = "Generators"
28
+ ANALYSIS = "Analysis Tools"
29
+ UTILITY = "Utility Tools"
30
+ MCP = "MCP Tools"
31
+
32
+
33
+ @dataclass
34
+ class ToolSpec:
35
+ """
36
+ Specification for a loadable tool.
37
+
38
+ Attributes:
39
+ name: Display name of the tool (used in UI)
40
+ module_path: Python module path (e.g., "tools.case_tool")
41
+ class_name: Name of the class to import (e.g., "CaseTool")
42
+ category: Tool category for organization
43
+ widget_class: Optional separate widget class name
44
+ dependencies: Optional list of required pip packages
45
+ description: Tool description for UI/help
46
+ is_widget: True if the class is a full widget (not just a tool class)
47
+ available_flag: Legacy flag name for backwards compatibility
48
+ """
49
+ name: str
50
+ module_path: str
51
+ class_name: str
52
+ category: ToolCategory = ToolCategory.UTILITY
53
+ widget_class: Optional[str] = None
54
+ dependencies: List[str] = field(default_factory=list)
55
+ description: str = ""
56
+ is_widget: bool = False
57
+ available_flag: str = "" # e.g., "CASE_TOOL_MODULE_AVAILABLE"
58
+
59
+
60
+ # Complete tool specifications registry
61
+ TOOL_SPECS: Dict[str, ToolSpec] = {
62
+ # Core Tools
63
+ "Case Tool": ToolSpec(
64
+ name="Case Tool",
65
+ module_path="tools.case_tool",
66
+ class_name="CaseTool",
67
+ category=ToolCategory.CORE,
68
+ description="Transform text case (uppercase, lowercase, title case, etc.)",
69
+ available_flag="CASE_TOOL_MODULE_AVAILABLE"
70
+ ),
71
+ "Find & Replace": ToolSpec(
72
+ name="Find & Replace",
73
+ module_path="tools.find_replace",
74
+ class_name="FindReplaceWidget",
75
+ category=ToolCategory.CORE,
76
+ is_widget=True,
77
+ description="Find and replace text with regex support",
78
+ available_flag="FIND_REPLACE_MODULE_AVAILABLE"
79
+ ),
80
+ "Diff Viewer": ToolSpec(
81
+ name="Diff Viewer",
82
+ module_path="tools.diff_viewer",
83
+ class_name="DiffViewerWidget",
84
+ widget_class="DiffViewerSettingsWidget",
85
+ category=ToolCategory.CORE,
86
+ is_widget=True,
87
+ description="Compare and view differences between texts",
88
+ available_flag="DIFF_VIEWER_MODULE_AVAILABLE"
89
+ ),
90
+
91
+ # AI Tools
92
+ "AI Tools": ToolSpec(
93
+ name="AI Tools",
94
+ module_path="tools.ai_tools",
95
+ class_name="AIToolsWidget",
96
+ category=ToolCategory.AI,
97
+ is_widget=True,
98
+ description="AI-powered text processing with multiple providers",
99
+ available_flag="AI_TOOLS_AVAILABLE"
100
+ ),
101
+
102
+ # Extraction Tools
103
+ "Email Extraction": ToolSpec(
104
+ name="Email Extraction",
105
+ module_path="tools.email_extraction_tool",
106
+ class_name="EmailExtractionTool",
107
+ category=ToolCategory.EXTRACTION,
108
+ description="Extract email addresses from text",
109
+ available_flag="EMAIL_EXTRACTION_MODULE_AVAILABLE"
110
+ ),
111
+ "Email Header Analyzer": ToolSpec(
112
+ name="Email Header Analyzer",
113
+ module_path="tools.email_header_analyzer",
114
+ class_name="EmailHeaderAnalyzer",
115
+ category=ToolCategory.EXTRACTION,
116
+ description="Analyze email headers for routing and authentication info",
117
+ available_flag="EMAIL_HEADER_ANALYZER_MODULE_AVAILABLE"
118
+ ),
119
+ "URL Link Extractor": ToolSpec(
120
+ name="URL Link Extractor",
121
+ module_path="tools.url_link_extractor",
122
+ class_name="URLLinkExtractor",
123
+ category=ToolCategory.EXTRACTION,
124
+ description="Extract URLs and links from text",
125
+ available_flag="URL_LINK_EXTRACTOR_MODULE_AVAILABLE"
126
+ ),
127
+ "Regex Extractor": ToolSpec(
128
+ name="Regex Extractor",
129
+ module_path="tools.regex_extractor",
130
+ class_name="RegexExtractor",
131
+ category=ToolCategory.EXTRACTION,
132
+ description="Extract text patterns using regular expressions",
133
+ available_flag="REGEX_EXTRACTOR_MODULE_AVAILABLE"
134
+ ),
135
+ "URL Parser": ToolSpec(
136
+ name="URL Parser",
137
+ module_path="tools.url_parser",
138
+ class_name="URLParser",
139
+ category=ToolCategory.EXTRACTION,
140
+ description="Parse and analyze URL components",
141
+ available_flag="URL_PARSER_MODULE_AVAILABLE"
142
+ ),
143
+ "HTML Tool": ToolSpec(
144
+ name="HTML Tool",
145
+ module_path="tools.html_tool",
146
+ class_name="HTMLExtractionTool",
147
+ category=ToolCategory.EXTRACTION,
148
+ description="Extract content from HTML",
149
+ available_flag="HTML_EXTRACTION_TOOL_MODULE_AVAILABLE"
150
+ ),
151
+ "Extraction Tools": ToolSpec(
152
+ name="Extraction Tools",
153
+ module_path="tools.extraction_tools",
154
+ class_name="ExtractionTools",
155
+ category=ToolCategory.EXTRACTION,
156
+ description="General purpose extraction utilities",
157
+ available_flag="EXTRACTION_TOOLS_MODULE_AVAILABLE"
158
+ ),
159
+
160
+ # Conversion Tools
161
+ "Base64 Tools": ToolSpec(
162
+ name="Base64 Tools",
163
+ module_path="tools.base64_tools",
164
+ class_name="Base64Tools",
165
+ widget_class="Base64ToolsWidget",
166
+ category=ToolCategory.CONVERSION,
167
+ description="Encode and decode Base64",
168
+ available_flag="BASE64_TOOLS_MODULE_AVAILABLE"
169
+ ),
170
+ "JSON/XML Tool": ToolSpec(
171
+ name="JSON/XML Tool",
172
+ module_path="tools.jsonxml_tool",
173
+ class_name="JSONXMLTool",
174
+ category=ToolCategory.CONVERSION,
175
+ description="Convert between JSON and XML formats",
176
+ available_flag="JSONXML_TOOL_MODULE_AVAILABLE"
177
+ ),
178
+ "Hash Generator": ToolSpec(
179
+ name="Hash Generator",
180
+ module_path="tools.hash_generator",
181
+ class_name="HashGenerator",
182
+ category=ToolCategory.CONVERSION,
183
+ description="Generate MD5, SHA1, SHA256 and other hashes",
184
+ available_flag="HASH_GENERATOR_MODULE_AVAILABLE"
185
+ ),
186
+ "Number Base Converter": ToolSpec(
187
+ name="Number Base Converter",
188
+ module_path="tools.number_base_converter",
189
+ class_name="NumberBaseConverter",
190
+ category=ToolCategory.CONVERSION,
191
+ description="Convert numbers between binary, octal, decimal, hex",
192
+ available_flag="NUMBER_BASE_CONVERTER_MODULE_AVAILABLE"
193
+ ),
194
+ "Timestamp Converter": ToolSpec(
195
+ name="Timestamp Converter",
196
+ module_path="tools.timestamp_converter",
197
+ class_name="TimestampConverter",
198
+ category=ToolCategory.CONVERSION,
199
+ description="Convert between timestamp formats",
200
+ available_flag="TIMESTAMP_CONVERTER_MODULE_AVAILABLE"
201
+ ),
202
+ "String Escape Tool": ToolSpec(
203
+ name="String Escape Tool",
204
+ module_path="tools.string_escape_tool",
205
+ class_name="StringEscapeTool",
206
+ category=ToolCategory.CONVERSION,
207
+ description="Escape/unescape strings for various formats",
208
+ available_flag="STRING_ESCAPE_TOOL_MODULE_AVAILABLE"
209
+ ),
210
+
211
+ # Text Manipulation Tools
212
+ "Sorter Tools": ToolSpec(
213
+ name="Sorter Tools",
214
+ module_path="tools.sorter_tools",
215
+ class_name="SorterTools",
216
+ category=ToolCategory.TEXT_MANIPULATION,
217
+ description="Sort lines alphabetically or numerically",
218
+ available_flag="SORTER_TOOLS_MODULE_AVAILABLE"
219
+ ),
220
+ "Line Tools": ToolSpec(
221
+ name="Line Tools",
222
+ module_path="tools.line_tools",
223
+ class_name="LineTools",
224
+ category=ToolCategory.TEXT_MANIPULATION,
225
+ description="Line manipulation (remove duplicates, number lines, etc.)",
226
+ available_flag="LINE_TOOLS_MODULE_AVAILABLE"
227
+ ),
228
+ "Whitespace Tools": ToolSpec(
229
+ name="Whitespace Tools",
230
+ module_path="tools.whitespace_tools",
231
+ class_name="WhitespaceTools",
232
+ category=ToolCategory.TEXT_MANIPULATION,
233
+ description="Trim, normalize whitespace and line endings",
234
+ available_flag="WHITESPACE_TOOLS_MODULE_AVAILABLE"
235
+ ),
236
+ "Column Tools": ToolSpec(
237
+ name="Column Tools",
238
+ module_path="tools.column_tools",
239
+ class_name="ColumnTools",
240
+ category=ToolCategory.TEXT_MANIPULATION,
241
+ description="CSV and column manipulation",
242
+ available_flag="COLUMN_TOOLS_MODULE_AVAILABLE"
243
+ ),
244
+ "Text Wrapper": ToolSpec(
245
+ name="Text Wrapper",
246
+ module_path="tools.text_wrapper",
247
+ class_name="TextWrapper",
248
+ category=ToolCategory.TEXT_MANIPULATION,
249
+ description="Wrap text to specified width",
250
+ available_flag="TEXT_WRAPPER_MODULE_AVAILABLE"
251
+ ),
252
+ "Markdown Tools": ToolSpec(
253
+ name="Markdown Tools",
254
+ module_path="tools.markdown_tools",
255
+ class_name="MarkdownTools",
256
+ category=ToolCategory.TEXT_MANIPULATION,
257
+ description="Process and extract from Markdown",
258
+ available_flag="MARKDOWN_TOOLS_MODULE_AVAILABLE"
259
+ ),
260
+ "Slug Generator": ToolSpec(
261
+ name="Slug Generator",
262
+ module_path="tools.slug_generator",
263
+ class_name="SlugGenerator",
264
+ category=ToolCategory.TEXT_MANIPULATION,
265
+ description="Generate URL-friendly slugs",
266
+ available_flag="SLUG_GENERATOR_MODULE_AVAILABLE"
267
+ ),
268
+ "Translator Tools": ToolSpec(
269
+ name="Translator Tools",
270
+ module_path="tools.translator_tools",
271
+ class_name="TranslatorTools",
272
+ category=ToolCategory.TEXT_MANIPULATION,
273
+ description="Translate to/from Morse code and binary",
274
+ available_flag="TRANSLATOR_TOOLS_MODULE_AVAILABLE"
275
+ ),
276
+
277
+ # Generator Tools
278
+ "Generator Tools": ToolSpec(
279
+ name="Generator Tools",
280
+ module_path="tools.generator_tools",
281
+ class_name="GeneratorTools",
282
+ widget_class="GeneratorToolsWidget",
283
+ category=ToolCategory.GENERATORS,
284
+ description="Generate passwords, UUIDs, Lorem Ipsum",
285
+ available_flag="GENERATOR_TOOLS_MODULE_AVAILABLE"
286
+ ),
287
+ "ASCII Art Generator": ToolSpec(
288
+ name="ASCII Art Generator",
289
+ module_path="tools.ascii_art_generator",
290
+ class_name="ASCIIArtGenerator",
291
+ category=ToolCategory.GENERATORS,
292
+ description="Generate ASCII art from text",
293
+ available_flag="ASCII_ART_GENERATOR_MODULE_AVAILABLE"
294
+ ),
295
+
296
+ # Analysis Tools
297
+ "Word Frequency Counter": ToolSpec(
298
+ name="Word Frequency Counter",
299
+ module_path="tools.word_frequency_counter",
300
+ class_name="WordFrequencyCounter",
301
+ category=ToolCategory.ANALYSIS,
302
+ description="Count word frequencies in text",
303
+ available_flag="WORD_FREQUENCY_COUNTER_MODULE_AVAILABLE"
304
+ ),
305
+ "Text Statistics": ToolSpec(
306
+ name="Text Statistics",
307
+ module_path="tools.text_statistics_tool",
308
+ class_name="TextStatistics",
309
+ category=ToolCategory.ANALYSIS,
310
+ description="Calculate text statistics (chars, words, lines)",
311
+ available_flag="TEXT_STATISTICS_MODULE_AVAILABLE"
312
+ ),
313
+ "Cron Tool": ToolSpec(
314
+ name="Cron Tool",
315
+ module_path="tools.cron_tool",
316
+ class_name="CronTool",
317
+ category=ToolCategory.ANALYSIS,
318
+ description="Parse and explain cron expressions",
319
+ available_flag="CRON_TOOL_MODULE_AVAILABLE"
320
+ ),
321
+
322
+ # Utility Tools
323
+ "cURL Tool": ToolSpec(
324
+ name="cURL Tool",
325
+ module_path="tools.curl_tool",
326
+ class_name="CurlToolWidget",
327
+ category=ToolCategory.UTILITY,
328
+ is_widget=True,
329
+ description="Make HTTP requests",
330
+ available_flag="CURL_TOOL_MODULE_AVAILABLE"
331
+ ),
332
+ "List Comparator": ToolSpec(
333
+ name="List Comparator",
334
+ module_path="tools.list_comparator",
335
+ class_name="DiffApp",
336
+ category=ToolCategory.UTILITY,
337
+ description="Compare two lists and find differences",
338
+ available_flag="LIST_COMPARATOR_MODULE_AVAILABLE"
339
+ ),
340
+ "Notes Widget": ToolSpec(
341
+ name="Notes Widget",
342
+ module_path="tools.notes_widget",
343
+ class_name="NotesWidget",
344
+ category=ToolCategory.UTILITY,
345
+ is_widget=True,
346
+ description="Save and manage notes",
347
+ available_flag="NOTES_WIDGET_MODULE_AVAILABLE"
348
+ ),
349
+ "Folder File Reporter": ToolSpec(
350
+ name="Folder File Reporter",
351
+ module_path="tools.folder_file_reporter_adapter",
352
+ class_name="FolderFileReporterAdapter",
353
+ category=ToolCategory.UTILITY,
354
+ description="Generate reports of folder contents",
355
+ available_flag="FOLDER_FILE_REPORTER_MODULE_AVAILABLE"
356
+ ),
357
+
358
+ # MCP Tools
359
+ "MCP Manager": ToolSpec(
360
+ name="MCP Manager",
361
+ module_path="tools.mcp_widget",
362
+ class_name="MCPManager",
363
+ category=ToolCategory.MCP,
364
+ is_widget=True,
365
+ description="Model Context Protocol server management",
366
+ available_flag="MCP_WIDGET_MODULE_AVAILABLE"
367
+ ),
368
+ }
369
+
370
+
371
+ class ToolLoader:
372
+ """
373
+ Centralized tool loading with lazy initialization.
374
+
375
+ Benefits:
376
+ - Single place to manage all tool imports
377
+ - Lazy loading - tools only loaded when first accessed
378
+ - Clean availability checking
379
+ - Reduces startup time
380
+ - Caches loaded modules and classes
381
+
382
+ Usage:
383
+ loader = get_tool_loader()
384
+
385
+ # Check if tool is available
386
+ if loader.is_available("Case Tool"):
387
+ # Get the tool class
388
+ CaseTool = loader.get_tool_class("Case Tool")
389
+ tool = CaseTool()
390
+
391
+ # Or create instance directly
392
+ tool = loader.create_instance("Case Tool")
393
+
394
+ # Get all available tools
395
+ available = loader.get_available_tools()
396
+ """
397
+
398
+ def __init__(self, tool_specs: Optional[Dict[str, ToolSpec]] = None):
399
+ """
400
+ Initialize the tool loader.
401
+
402
+ Args:
403
+ tool_specs: Optional custom tool specifications (uses TOOL_SPECS if None)
404
+ """
405
+ self._specs = tool_specs or TOOL_SPECS.copy()
406
+ self._loaded_modules: Dict[str, Any] = {}
407
+ self._loaded_classes: Dict[str, Type] = {}
408
+ self._availability_cache: Dict[str, bool] = {}
409
+ self._load_errors: Dict[str, str] = {}
410
+ self._widget_classes: Dict[str, Type] = {}
411
+
412
+ def register_tool(self, spec: ToolSpec) -> None:
413
+ """
414
+ Register a new tool specification.
415
+
416
+ Args:
417
+ spec: Tool specification to register
418
+ """
419
+ self._specs[spec.name] = spec
420
+ # Clear caches for this tool
421
+ self._availability_cache.pop(spec.name, None)
422
+ self._loaded_classes.pop(spec.name, None)
423
+ self._load_errors.pop(spec.name, None)
424
+ logger.debug(f"Registered tool: {spec.name}")
425
+
426
+ def unregister_tool(self, name: str) -> bool:
427
+ """
428
+ Unregister a tool.
429
+
430
+ Args:
431
+ name: Tool name to unregister
432
+
433
+ Returns:
434
+ True if tool was found and removed
435
+ """
436
+ if name in self._specs:
437
+ del self._specs[name]
438
+ self._availability_cache.pop(name, None)
439
+ self._loaded_classes.pop(name, None)
440
+ self._load_errors.pop(name, None)
441
+ return True
442
+ return False
443
+
444
+ def is_available(self, tool_name: str) -> bool:
445
+ """
446
+ Check if a tool is available (can be imported).
447
+
448
+ Args:
449
+ tool_name: Name of the tool
450
+
451
+ Returns:
452
+ True if tool module can be imported
453
+ """
454
+ if tool_name in self._availability_cache:
455
+ return self._availability_cache[tool_name]
456
+
457
+ if tool_name not in self._specs:
458
+ self._availability_cache[tool_name] = False
459
+ return False
460
+
461
+ spec = self._specs[tool_name]
462
+ try:
463
+ importlib.import_module(spec.module_path)
464
+ self._availability_cache[tool_name] = True
465
+ return True
466
+ except ImportError as e:
467
+ self._availability_cache[tool_name] = False
468
+ self._load_errors[tool_name] = str(e)
469
+ logger.debug(f"Tool '{tool_name}' not available: {e}")
470
+ return False
471
+
472
+ def get_tool_class(self, tool_name: str) -> Optional[Type]:
473
+ """
474
+ Get the tool class (lazy loaded).
475
+
476
+ Args:
477
+ tool_name: Name of the tool
478
+
479
+ Returns:
480
+ The tool class, or None if not available
481
+ """
482
+ if tool_name in self._loaded_classes:
483
+ return self._loaded_classes[tool_name]
484
+
485
+ if not self.is_available(tool_name):
486
+ return None
487
+
488
+ spec = self._specs[tool_name]
489
+ try:
490
+ module = self._get_module(spec.module_path)
491
+ tool_class = getattr(module, spec.class_name)
492
+ self._loaded_classes[tool_name] = tool_class
493
+ logger.info(f"Loaded tool class: {tool_name}")
494
+ return tool_class
495
+ except (ImportError, AttributeError) as e:
496
+ self._load_errors[tool_name] = str(e)
497
+ logger.error(f"Failed to load tool class '{tool_name}': {e}")
498
+ return None
499
+
500
+ def get_widget_class(self, tool_name: str) -> Optional[Type]:
501
+ """
502
+ Get the widget class for a tool (if it has a separate one).
503
+
504
+ Args:
505
+ tool_name: Name of the tool
506
+
507
+ Returns:
508
+ The widget class, or None if not available
509
+ """
510
+ if tool_name in self._widget_classes:
511
+ return self._widget_classes[tool_name]
512
+
513
+ if tool_name not in self._specs:
514
+ return None
515
+
516
+ spec = self._specs[tool_name]
517
+
518
+ # If no separate widget class, return the main class
519
+ if not spec.widget_class:
520
+ return self.get_tool_class(tool_name)
521
+
522
+ try:
523
+ module = self._get_module(spec.module_path)
524
+ widget_class = getattr(module, spec.widget_class)
525
+ self._widget_classes[tool_name] = widget_class
526
+ return widget_class
527
+ except (ImportError, AttributeError) as e:
528
+ logger.error(f"Failed to load widget class for '{tool_name}': {e}")
529
+ return None
530
+
531
+ def create_instance(self, tool_name: str, *args, **kwargs) -> Optional[Any]:
532
+ """
533
+ Create an instance of a tool.
534
+
535
+ Args:
536
+ tool_name: Name of the tool
537
+ *args, **kwargs: Arguments to pass to the constructor
538
+
539
+ Returns:
540
+ Tool instance, or None if not available
541
+ """
542
+ tool_class = self.get_tool_class(tool_name)
543
+ if tool_class is None:
544
+ return None
545
+
546
+ try:
547
+ return tool_class(*args, **kwargs)
548
+ except Exception as e:
549
+ logger.error(f"Failed to instantiate tool '{tool_name}': {e}")
550
+ return None
551
+
552
+ def _get_module(self, module_path: str) -> Any:
553
+ """Get a module (cached)."""
554
+ if module_path not in self._loaded_modules:
555
+ self._loaded_modules[module_path] = importlib.import_module(module_path)
556
+ return self._loaded_modules[module_path]
557
+
558
+ def get_available_tools(self) -> List[str]:
559
+ """
560
+ Get list of all available tool names.
561
+
562
+ Returns:
563
+ List of tool names that can be loaded
564
+ """
565
+ return [name for name in self._specs.keys() if self.is_available(name)]
566
+
567
+ def get_all_tool_names(self) -> List[str]:
568
+ """
569
+ Get list of all registered tool names.
570
+
571
+ Returns:
572
+ List of all tool names (whether available or not)
573
+ """
574
+ return list(self._specs.keys())
575
+
576
+ def get_tools_by_category(self, category: ToolCategory) -> List[str]:
577
+ """
578
+ Get tools in a specific category.
579
+
580
+ Args:
581
+ category: Tool category
582
+
583
+ Returns:
584
+ List of tool names in that category
585
+ """
586
+ return [
587
+ name for name, spec in self._specs.items()
588
+ if spec.category == category and self.is_available(name)
589
+ ]
590
+
591
+ def get_tool_spec(self, tool_name: str) -> Optional[ToolSpec]:
592
+ """
593
+ Get the specification for a tool.
594
+
595
+ Args:
596
+ tool_name: Name of the tool
597
+
598
+ Returns:
599
+ ToolSpec or None if not found
600
+ """
601
+ return self._specs.get(tool_name)
602
+
603
+ def get_load_error(self, tool_name: str) -> Optional[str]:
604
+ """
605
+ Get the error message if a tool failed to load.
606
+
607
+ Args:
608
+ tool_name: Name of the tool
609
+
610
+ Returns:
611
+ Error message, or None if no error
612
+ """
613
+ return self._load_errors.get(tool_name)
614
+
615
+ def get_availability_report(self) -> Dict[str, Dict[str, Any]]:
616
+ """
617
+ Get a report of all tools and their availability.
618
+
619
+ Returns:
620
+ Dictionary with tool availability information
621
+ """
622
+ report = {}
623
+ for name, spec in self._specs.items():
624
+ available = self.is_available(name)
625
+ report[name] = {
626
+ 'available': available,
627
+ 'category': spec.category.value,
628
+ 'module': spec.module_path,
629
+ 'class': spec.class_name,
630
+ 'is_widget': spec.is_widget,
631
+ 'error': self._load_errors.get(name) if not available else None
632
+ }
633
+ return report
634
+
635
+ def preload_tools(self, tool_names: Optional[List[str]] = None) -> Dict[str, bool]:
636
+ """
637
+ Preload specified tools (or all if none specified).
638
+
639
+ Args:
640
+ tool_names: List of tool names to preload, or None for all
641
+
642
+ Returns:
643
+ Dictionary of tool names to load success status
644
+ """
645
+ names = tool_names or list(self._specs.keys())
646
+ results = {}
647
+ for name in names:
648
+ results[name] = self.get_tool_class(name) is not None
649
+ return results
650
+
651
+ def clear_cache(self) -> None:
652
+ """Clear all cached modules and classes."""
653
+ self._loaded_modules.clear()
654
+ self._loaded_classes.clear()
655
+ self._availability_cache.clear()
656
+ self._load_errors.clear()
657
+ self._widget_classes.clear()
658
+ logger.debug("Tool loader cache cleared")
659
+
660
+ def get_legacy_flags(self) -> Dict[str, bool]:
661
+ """
662
+ Get legacy availability flags for backwards compatibility.
663
+
664
+ Returns:
665
+ Dictionary of flag names to boolean values
666
+ """
667
+ flags = {}
668
+ for name, spec in self._specs.items():
669
+ if spec.available_flag:
670
+ flags[spec.available_flag] = self.is_available(name)
671
+ return flags
672
+
673
+
674
+ # Global instance
675
+ _tool_loader: Optional[ToolLoader] = None
676
+
677
+
678
+ def get_tool_loader() -> ToolLoader:
679
+ """
680
+ Get the global tool loader instance.
681
+
682
+ Returns:
683
+ Global ToolLoader instance
684
+ """
685
+ global _tool_loader
686
+ if _tool_loader is None:
687
+ _tool_loader = ToolLoader()
688
+ return _tool_loader
689
+
690
+
691
+ def init_tool_loader(tool_specs: Optional[Dict[str, ToolSpec]] = None) -> ToolLoader:
692
+ """
693
+ Initialize the global tool loader.
694
+
695
+ Args:
696
+ tool_specs: Optional custom tool specifications
697
+
698
+ Returns:
699
+ Initialized ToolLoader
700
+ """
701
+ global _tool_loader
702
+ _tool_loader = ToolLoader(tool_specs)
703
+ return _tool_loader
704
+
705
+
706
+ def reset_tool_loader() -> None:
707
+ """Reset the global tool loader."""
708
+ global _tool_loader
709
+ _tool_loader = None
710
+