pomera-ai-commander 0.1.0 → 1.2.1

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 (191) 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 +1033 -1033
  9. package/core/content_hash_cache.py +508 -508
  10. package/core/context_menu.py +313 -313
  11. package/core/data_validator.py +1066 -1066
  12. package/core/database_connection_manager.py +744 -744
  13. package/core/database_curl_settings_manager.py +608 -608
  14. package/core/database_promera_ai_settings_manager.py +446 -446
  15. package/core/database_schema.py +411 -411
  16. package/core/database_schema_manager.py +395 -395
  17. package/core/database_settings_manager.py +1507 -1507
  18. package/core/database_settings_manager_interface.py +456 -456
  19. package/core/dialog_manager.py +734 -734
  20. package/core/efficient_line_numbers.py +510 -510
  21. package/core/error_handler.py +746 -746
  22. package/core/error_service.py +431 -431
  23. package/core/event_consolidator.py +511 -511
  24. package/core/mcp/__init__.py +43 -43
  25. package/core/mcp/protocol.py +288 -288
  26. package/core/mcp/schema.py +251 -251
  27. package/core/mcp/server_stdio.py +299 -299
  28. package/core/mcp/tool_registry.py +2372 -2345
  29. package/core/memory_efficient_text_widget.py +711 -711
  30. package/core/migration_manager.py +914 -914
  31. package/core/migration_test_suite.py +1085 -1085
  32. package/core/migration_validator.py +1143 -1143
  33. package/core/optimized_find_replace.py +714 -714
  34. package/core/optimized_pattern_engine.py +424 -424
  35. package/core/optimized_search_highlighter.py +552 -552
  36. package/core/performance_monitor.py +674 -674
  37. package/core/persistence_manager.py +712 -712
  38. package/core/progressive_stats_calculator.py +632 -632
  39. package/core/regex_pattern_cache.py +529 -529
  40. package/core/regex_pattern_library.py +350 -350
  41. package/core/search_operation_manager.py +434 -434
  42. package/core/settings_defaults_registry.py +1087 -1087
  43. package/core/settings_integrity_validator.py +1111 -1111
  44. package/core/settings_serializer.py +557 -557
  45. package/core/settings_validator.py +1823 -1823
  46. package/core/smart_stats_calculator.py +709 -709
  47. package/core/statistics_update_manager.py +619 -619
  48. package/core/stats_config_manager.py +858 -858
  49. package/core/streaming_text_handler.py +723 -723
  50. package/core/task_scheduler.py +596 -596
  51. package/core/update_pattern_library.py +168 -168
  52. package/core/visibility_monitor.py +596 -596
  53. package/core/widget_cache.py +498 -498
  54. package/mcp.json +51 -61
  55. package/package.json +61 -57
  56. package/pomera.py +7482 -7482
  57. package/pomera_mcp_server.py +183 -144
  58. package/requirements.txt +32 -0
  59. package/tools/__init__.py +4 -4
  60. package/tools/ai_tools.py +2891 -2891
  61. package/tools/ascii_art_generator.py +352 -352
  62. package/tools/base64_tools.py +183 -183
  63. package/tools/base_tool.py +511 -511
  64. package/tools/case_tool.py +308 -308
  65. package/tools/column_tools.py +395 -395
  66. package/tools/cron_tool.py +884 -884
  67. package/tools/curl_history.py +600 -600
  68. package/tools/curl_processor.py +1207 -1207
  69. package/tools/curl_settings.py +502 -502
  70. package/tools/curl_tool.py +5467 -5467
  71. package/tools/diff_viewer.py +1071 -1071
  72. package/tools/email_extraction_tool.py +248 -248
  73. package/tools/email_header_analyzer.py +425 -425
  74. package/tools/extraction_tools.py +250 -250
  75. package/tools/find_replace.py +1750 -1750
  76. package/tools/folder_file_reporter.py +1463 -1463
  77. package/tools/folder_file_reporter_adapter.py +480 -480
  78. package/tools/generator_tools.py +1216 -1216
  79. package/tools/hash_generator.py +255 -255
  80. package/tools/html_tool.py +656 -656
  81. package/tools/jsonxml_tool.py +729 -729
  82. package/tools/line_tools.py +419 -419
  83. package/tools/markdown_tools.py +561 -561
  84. package/tools/mcp_widget.py +1417 -1417
  85. package/tools/notes_widget.py +973 -973
  86. package/tools/number_base_converter.py +372 -372
  87. package/tools/regex_extractor.py +571 -571
  88. package/tools/slug_generator.py +310 -310
  89. package/tools/sorter_tools.py +458 -458
  90. package/tools/string_escape_tool.py +392 -392
  91. package/tools/text_statistics_tool.py +365 -365
  92. package/tools/text_wrapper.py +430 -430
  93. package/tools/timestamp_converter.py +421 -421
  94. package/tools/tool_loader.py +710 -710
  95. package/tools/translator_tools.py +522 -522
  96. package/tools/url_link_extractor.py +261 -261
  97. package/tools/url_parser.py +204 -204
  98. package/tools/whitespace_tools.py +355 -355
  99. package/tools/word_frequency_counter.py +146 -146
  100. package/core/__pycache__/__init__.cpython-313.pyc +0 -0
  101. package/core/__pycache__/app_context.cpython-313.pyc +0 -0
  102. package/core/__pycache__/async_text_processor.cpython-313.pyc +0 -0
  103. package/core/__pycache__/backup_manager.cpython-313.pyc +0 -0
  104. package/core/__pycache__/backup_recovery_manager.cpython-313.pyc +0 -0
  105. package/core/__pycache__/content_hash_cache.cpython-313.pyc +0 -0
  106. package/core/__pycache__/context_menu.cpython-313.pyc +0 -0
  107. package/core/__pycache__/data_validator.cpython-313.pyc +0 -0
  108. package/core/__pycache__/database_connection_manager.cpython-313.pyc +0 -0
  109. package/core/__pycache__/database_curl_settings_manager.cpython-313.pyc +0 -0
  110. package/core/__pycache__/database_promera_ai_settings_manager.cpython-313.pyc +0 -0
  111. package/core/__pycache__/database_schema.cpython-313.pyc +0 -0
  112. package/core/__pycache__/database_schema_manager.cpython-313.pyc +0 -0
  113. package/core/__pycache__/database_settings_manager.cpython-313.pyc +0 -0
  114. package/core/__pycache__/database_settings_manager_interface.cpython-313.pyc +0 -0
  115. package/core/__pycache__/dialog_manager.cpython-313.pyc +0 -0
  116. package/core/__pycache__/efficient_line_numbers.cpython-313.pyc +0 -0
  117. package/core/__pycache__/error_handler.cpython-313.pyc +0 -0
  118. package/core/__pycache__/error_service.cpython-313.pyc +0 -0
  119. package/core/__pycache__/event_consolidator.cpython-313.pyc +0 -0
  120. package/core/__pycache__/memory_efficient_text_widget.cpython-313.pyc +0 -0
  121. package/core/__pycache__/migration_manager.cpython-313.pyc +0 -0
  122. package/core/__pycache__/migration_test_suite.cpython-313.pyc +0 -0
  123. package/core/__pycache__/migration_validator.cpython-313.pyc +0 -0
  124. package/core/__pycache__/optimized_find_replace.cpython-313.pyc +0 -0
  125. package/core/__pycache__/optimized_pattern_engine.cpython-313.pyc +0 -0
  126. package/core/__pycache__/optimized_search_highlighter.cpython-313.pyc +0 -0
  127. package/core/__pycache__/performance_monitor.cpython-313.pyc +0 -0
  128. package/core/__pycache__/persistence_manager.cpython-313.pyc +0 -0
  129. package/core/__pycache__/progressive_stats_calculator.cpython-313.pyc +0 -0
  130. package/core/__pycache__/regex_pattern_cache.cpython-313.pyc +0 -0
  131. package/core/__pycache__/regex_pattern_library.cpython-313.pyc +0 -0
  132. package/core/__pycache__/search_operation_manager.cpython-313.pyc +0 -0
  133. package/core/__pycache__/settings_defaults_registry.cpython-313.pyc +0 -0
  134. package/core/__pycache__/settings_integrity_validator.cpython-313.pyc +0 -0
  135. package/core/__pycache__/settings_serializer.cpython-313.pyc +0 -0
  136. package/core/__pycache__/settings_validator.cpython-313.pyc +0 -0
  137. package/core/__pycache__/smart_stats_calculator.cpython-313.pyc +0 -0
  138. package/core/__pycache__/statistics_update_manager.cpython-313.pyc +0 -0
  139. package/core/__pycache__/stats_config_manager.cpython-313.pyc +0 -0
  140. package/core/__pycache__/streaming_text_handler.cpython-313.pyc +0 -0
  141. package/core/__pycache__/task_scheduler.cpython-313.pyc +0 -0
  142. package/core/__pycache__/visibility_monitor.cpython-313.pyc +0 -0
  143. package/core/__pycache__/widget_cache.cpython-313.pyc +0 -0
  144. package/core/mcp/__pycache__/__init__.cpython-313.pyc +0 -0
  145. package/core/mcp/__pycache__/protocol.cpython-313.pyc +0 -0
  146. package/core/mcp/__pycache__/schema.cpython-313.pyc +0 -0
  147. package/core/mcp/__pycache__/server_stdio.cpython-313.pyc +0 -0
  148. package/core/mcp/__pycache__/tool_registry.cpython-313.pyc +0 -0
  149. package/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  150. package/tools/__pycache__/ai_tools.cpython-313.pyc +0 -0
  151. package/tools/__pycache__/ascii_art_generator.cpython-313.pyc +0 -0
  152. package/tools/__pycache__/base64_tools.cpython-313.pyc +0 -0
  153. package/tools/__pycache__/base_tool.cpython-313.pyc +0 -0
  154. package/tools/__pycache__/case_tool.cpython-313.pyc +0 -0
  155. package/tools/__pycache__/column_tools.cpython-313.pyc +0 -0
  156. package/tools/__pycache__/cron_tool.cpython-313.pyc +0 -0
  157. package/tools/__pycache__/curl_history.cpython-313.pyc +0 -0
  158. package/tools/__pycache__/curl_processor.cpython-313.pyc +0 -0
  159. package/tools/__pycache__/curl_settings.cpython-313.pyc +0 -0
  160. package/tools/__pycache__/curl_tool.cpython-313.pyc +0 -0
  161. package/tools/__pycache__/diff_viewer.cpython-313.pyc +0 -0
  162. package/tools/__pycache__/email_extraction_tool.cpython-313.pyc +0 -0
  163. package/tools/__pycache__/email_header_analyzer.cpython-313.pyc +0 -0
  164. package/tools/__pycache__/extraction_tools.cpython-313.pyc +0 -0
  165. package/tools/__pycache__/find_replace.cpython-313.pyc +0 -0
  166. package/tools/__pycache__/folder_file_reporter.cpython-313.pyc +0 -0
  167. package/tools/__pycache__/folder_file_reporter_adapter.cpython-313.pyc +0 -0
  168. package/tools/__pycache__/generator_tools.cpython-313.pyc +0 -0
  169. package/tools/__pycache__/hash_generator.cpython-313.pyc +0 -0
  170. package/tools/__pycache__/html_tool.cpython-313.pyc +0 -0
  171. package/tools/__pycache__/huggingface_helper.cpython-313.pyc +0 -0
  172. package/tools/__pycache__/jsonxml_tool.cpython-313.pyc +0 -0
  173. package/tools/__pycache__/line_tools.cpython-313.pyc +0 -0
  174. package/tools/__pycache__/list_comparator.cpython-313.pyc +0 -0
  175. package/tools/__pycache__/markdown_tools.cpython-313.pyc +0 -0
  176. package/tools/__pycache__/mcp_widget.cpython-313.pyc +0 -0
  177. package/tools/__pycache__/notes_widget.cpython-313.pyc +0 -0
  178. package/tools/__pycache__/number_base_converter.cpython-313.pyc +0 -0
  179. package/tools/__pycache__/regex_extractor.cpython-313.pyc +0 -0
  180. package/tools/__pycache__/slug_generator.cpython-313.pyc +0 -0
  181. package/tools/__pycache__/sorter_tools.cpython-313.pyc +0 -0
  182. package/tools/__pycache__/string_escape_tool.cpython-313.pyc +0 -0
  183. package/tools/__pycache__/text_statistics_tool.cpython-313.pyc +0 -0
  184. package/tools/__pycache__/text_wrapper.cpython-313.pyc +0 -0
  185. package/tools/__pycache__/timestamp_converter.cpython-313.pyc +0 -0
  186. package/tools/__pycache__/tool_loader.cpython-313.pyc +0 -0
  187. package/tools/__pycache__/translator_tools.cpython-313.pyc +0 -0
  188. package/tools/__pycache__/url_link_extractor.cpython-313.pyc +0 -0
  189. package/tools/__pycache__/url_parser.cpython-313.pyc +0 -0
  190. package/tools/__pycache__/whitespace_tools.cpython-313.pyc +0 -0
  191. package/tools/__pycache__/word_frequency_counter.cpython-313.pyc +0 -0
@@ -1,503 +1,503 @@
1
- """
2
- cURL Tool Settings Management Module
3
-
4
- This module provides settings persistence and management for the cURL GUI Tool.
5
- It handles configuration storage, loading, and validation for tool settings.
6
-
7
- Author: Pomera AI Commander
8
- """
9
-
10
- import json
11
- import os
12
- from typing import Dict, Any, Optional
13
- import logging
14
- from datetime import datetime
15
-
16
-
17
- class CurlSettingsManager:
18
- """
19
- Manages settings persistence and configuration for the cURL Tool.
20
-
21
- Handles:
22
- - Settings file storage and loading
23
- - Default configuration values
24
- - Settings validation
25
- - Configuration backup and restore
26
- """
27
-
28
- def __init__(self, settings_file: str = "settings.json", logger=None):
29
- """
30
- Initialize the settings manager.
31
-
32
- Args:
33
- settings_file: Path to the main settings file
34
- logger: Logger instance for debugging
35
- """
36
- self.settings_file = settings_file
37
- self.logger = logger or logging.getLogger(__name__)
38
- self.tool_key = "cURL Tool" # Key in tool_settings section
39
-
40
- # Default settings configuration
41
- self.default_settings = {
42
- # Request settings
43
- "default_timeout": 30,
44
- "follow_redirects": True,
45
- "verify_ssl": True,
46
- "max_redirects": 10,
47
- "user_agent": "Pomera cURL Tool/1.0",
48
-
49
- # History settings
50
- "save_history": True,
51
- "max_history_items": 100,
52
- "auto_cleanup_history": True,
53
- "history_retention_days": 30,
54
-
55
- # Authentication settings
56
- "persist_auth": True,
57
- "auth_timeout_minutes": 60,
58
- "clear_auth_on_exit": False,
59
-
60
- # UI settings
61
- "remember_window_size": True,
62
- "default_body_type": "JSON",
63
- "auto_format_json": True,
64
- "syntax_highlighting": True,
65
- "show_response_time": True,
66
-
67
- # Download settings
68
- "default_download_path": "",
69
- "use_remote_filename": True,
70
- "resume_downloads": True,
71
- "download_chunk_size": 8192,
72
-
73
- # Export/Import settings
74
- "curl_export_format": "standard", # standard, minimal, verbose
75
- "include_comments_in_export": True,
76
- "auto_escape_special_chars": True,
77
- "complex_options": "", # Additional cURL options not handled by UI
78
-
79
- # Debug settings
80
- "enable_debug_logging": False,
81
- "log_request_headers": True,
82
- "log_response_headers": True,
83
- "max_log_size_mb": 10,
84
-
85
- # Advanced settings
86
- "connection_pool_size": 10,
87
- "retry_attempts": 3,
88
- "retry_delay_seconds": 1,
89
- "enable_http2": False,
90
-
91
- # Version and metadata
92
- "settings_version": "1.0",
93
- "last_updated": None,
94
- "created_date": None
95
- }
96
-
97
- # Current settings (loaded from file or defaults)
98
- self.settings = {}
99
-
100
- # Load settings on initialization
101
- self.load_settings()
102
-
103
- def load_settings(self) -> Dict[str, Any]:
104
- """
105
- Load settings from centralized settings.json file.
106
-
107
- Returns:
108
- Dictionary of current settings
109
- """
110
- try:
111
- if os.path.exists(self.settings_file):
112
- with open(self.settings_file, 'r', encoding='utf-8') as f:
113
- all_settings = json.load(f)
114
-
115
- # Get tool settings from tool_settings section
116
- tool_settings = all_settings.get("tool_settings", {})
117
- file_settings = tool_settings.get(self.tool_key, {})
118
-
119
- # Merge with defaults to ensure all keys exist
120
- self.settings = self.default_settings.copy()
121
- self.settings.update(file_settings)
122
-
123
- # Validate and fix any invalid settings
124
- self._validate_settings()
125
-
126
- self.logger.info(f"cURL Tool settings loaded from {self.settings_file}")
127
- else:
128
- # Use defaults and create file
129
- self.settings = self.default_settings.copy()
130
- self.settings["created_date"] = datetime.now().isoformat()
131
- self.save_settings()
132
- self.logger.info("Created new settings file with defaults")
133
-
134
- except Exception as e:
135
- self.logger.error(f"Error loading settings: {e}")
136
- # Fall back to defaults
137
- self.settings = self.default_settings.copy()
138
- self.settings["created_date"] = datetime.now().isoformat()
139
-
140
- return self.settings
141
-
142
- def save_settings(self) -> bool:
143
- """
144
- Save current settings to centralized settings.json file.
145
-
146
- Returns:
147
- True if successful, False otherwise
148
- """
149
- try:
150
- # Update last modified timestamp
151
- self.settings["last_updated"] = datetime.now().isoformat()
152
-
153
- # Load existing settings file
154
- all_settings = {}
155
- if os.path.exists(self.settings_file):
156
- with open(self.settings_file, 'r', encoding='utf-8') as f:
157
- all_settings = json.load(f)
158
-
159
- # Ensure tool_settings section exists
160
- if "tool_settings" not in all_settings:
161
- all_settings["tool_settings"] = {}
162
-
163
- # Update cURL Tool settings (merge with existing data to preserve history)
164
- if self.tool_key not in all_settings["tool_settings"]:
165
- all_settings["tool_settings"][self.tool_key] = {}
166
-
167
- # Preserve existing non-settings data (like history, collections)
168
- existing_data = all_settings["tool_settings"][self.tool_key]
169
-
170
- # Update with current settings
171
- all_settings["tool_settings"][self.tool_key] = {**existing_data, **self.settings}
172
-
173
- # Create backup of existing settings
174
- self._create_backup()
175
-
176
- # Write settings to file
177
- with open(self.settings_file, 'w', encoding='utf-8') as f:
178
- json.dump(all_settings, f, indent=4, ensure_ascii=False)
179
-
180
- self.logger.info(f"cURL Tool settings saved to {self.settings_file}")
181
- return True
182
-
183
- except Exception as e:
184
- self.logger.error(f"Error saving settings: {e}")
185
- return False
186
-
187
- def get_setting(self, key: str, default: Any = None) -> Any:
188
- """
189
- Get a setting value.
190
-
191
- Args:
192
- key: Setting key
193
- default: Default value if key not found
194
-
195
- Returns:
196
- Setting value or default
197
- """
198
- return self.settings.get(key, default)
199
-
200
- def set_setting(self, key: str, value: Any) -> bool:
201
- """
202
- Set a setting value.
203
-
204
- Args:
205
- key: Setting key
206
- value: Setting value
207
-
208
- Returns:
209
- True if successful, False otherwise
210
- """
211
- try:
212
- # Validate the setting
213
- if self._validate_setting(key, value):
214
- self.settings[key] = value
215
- return True
216
- else:
217
- self.logger.warning(f"Invalid setting value: {key} = {value}")
218
- return False
219
-
220
- except Exception as e:
221
- self.logger.error(f"Error setting {key}: {e}")
222
- return False
223
-
224
- def reset_to_defaults(self) -> bool:
225
- """
226
- Reset all settings to defaults.
227
-
228
- Returns:
229
- True if successful, False otherwise
230
- """
231
- try:
232
- # Keep creation date if it exists
233
- created_date = self.settings.get("created_date")
234
-
235
- self.settings = self.default_settings.copy()
236
- if created_date:
237
- self.settings["created_date"] = created_date
238
-
239
- return self.save_settings()
240
-
241
- except Exception as e:
242
- self.logger.error(f"Error resetting settings: {e}")
243
- return False
244
-
245
- def export_settings(self, filepath: str) -> bool:
246
- """
247
- Export settings to a file.
248
-
249
- Args:
250
- filepath: Path to export file
251
-
252
- Returns:
253
- True if successful, False otherwise
254
- """
255
- try:
256
- export_data = {
257
- "settings": self.settings,
258
- "export_date": datetime.now().isoformat(),
259
- "version": self.settings.get("settings_version", "1.0")
260
- }
261
-
262
- with open(filepath, 'w', encoding='utf-8') as f:
263
- json.dump(export_data, f, indent=2, ensure_ascii=False)
264
-
265
- self.logger.info(f"Settings exported to {filepath}")
266
- return True
267
-
268
- except Exception as e:
269
- self.logger.error(f"Error exporting settings: {e}")
270
- return False
271
-
272
- def import_settings(self, filepath: str) -> bool:
273
- """
274
- Import settings from a file.
275
-
276
- Args:
277
- filepath: Path to import file
278
-
279
- Returns:
280
- True if successful, False otherwise
281
- """
282
- try:
283
- with open(filepath, 'r', encoding='utf-8') as f:
284
- import_data = json.load(f)
285
-
286
- # Extract settings from import data
287
- if "settings" in import_data:
288
- imported_settings = import_data["settings"]
289
- else:
290
- # Assume the file contains settings directly
291
- imported_settings = import_data
292
-
293
- # Merge with current settings (don't overwrite everything)
294
- for key, value in imported_settings.items():
295
- if key in self.default_settings:
296
- if self._validate_setting(key, value):
297
- self.settings[key] = value
298
-
299
- # Save the updated settings
300
- return self.save_settings()
301
-
302
- except Exception as e:
303
- self.logger.error(f"Error importing settings: {e}")
304
- return False
305
-
306
- def get_all_settings(self) -> Dict[str, Any]:
307
- """
308
- Get all current settings.
309
-
310
- Returns:
311
- Dictionary of all settings
312
- """
313
- return self.settings.copy()
314
-
315
- def get_default_settings(self) -> Dict[str, Any]:
316
- """
317
- Get default settings.
318
-
319
- Returns:
320
- Dictionary of default settings
321
- """
322
- return self.default_settings.copy()
323
-
324
- def _validate_settings(self):
325
- """Validate and fix invalid settings."""
326
- # Ensure numeric settings are within valid ranges
327
- numeric_ranges = {
328
- "default_timeout": (1, 300),
329
- "max_redirects": (0, 50),
330
- "max_history_items": (10, 1000),
331
- "history_retention_days": (1, 365),
332
- "auth_timeout_minutes": (5, 1440),
333
- "download_chunk_size": (1024, 1048576),
334
- "connection_pool_size": (1, 100),
335
- "retry_attempts": (0, 10),
336
- "retry_delay_seconds": (0, 60),
337
- "max_log_size_mb": (1, 100)
338
- }
339
-
340
- for key, (min_val, max_val) in numeric_ranges.items():
341
- if key in self.settings:
342
- value = self.settings[key]
343
- if not isinstance(value, (int, float)) or value < min_val or value > max_val:
344
- self.settings[key] = self.default_settings[key]
345
- self.logger.warning(f"Reset invalid setting {key} to default")
346
-
347
- # Ensure string settings are not empty where required
348
- required_strings = ["user_agent", "curl_export_format", "settings_version"]
349
- for key in required_strings:
350
- if key in self.settings and not isinstance(self.settings[key], str):
351
- self.settings[key] = self.default_settings[key]
352
- self.logger.warning(f"Reset invalid string setting {key} to default")
353
-
354
- # Ensure boolean settings are actually boolean
355
- boolean_settings = [
356
- "follow_redirects", "verify_ssl", "save_history", "auto_cleanup_history",
357
- "persist_auth", "clear_auth_on_exit", "remember_window_size",
358
- "auto_format_json", "syntax_highlighting", "show_response_time",
359
- "use_remote_filename", "resume_downloads", "include_comments_in_export",
360
- "auto_escape_special_chars", "enable_debug_logging", "log_request_headers",
361
- "log_response_headers", "enable_http2"
362
- ]
363
-
364
- for key in boolean_settings:
365
- if key in self.settings and not isinstance(self.settings[key], bool):
366
- self.settings[key] = self.default_settings[key]
367
- self.logger.warning(f"Reset invalid boolean setting {key} to default")
368
-
369
- def _validate_setting(self, key: str, value: Any) -> bool:
370
- """
371
- Validate a single setting value.
372
-
373
- Args:
374
- key: Setting key
375
- value: Setting value
376
-
377
- Returns:
378
- True if valid, False otherwise
379
- """
380
- if key not in self.default_settings:
381
- return False
382
-
383
- # Type validation
384
- expected_type = type(self.default_settings[key])
385
- if not isinstance(value, expected_type):
386
- # Allow None for optional settings
387
- if value is None and key in ["default_download_path", "last_updated", "created_date"]:
388
- return True
389
- return False
390
-
391
- # Range validation for numeric values
392
- numeric_ranges = {
393
- "default_timeout": (1, 300),
394
- "max_redirects": (0, 50),
395
- "max_history_items": (10, 1000),
396
- "history_retention_days": (1, 365),
397
- "auth_timeout_minutes": (5, 1440),
398
- "download_chunk_size": (1024, 1048576),
399
- "connection_pool_size": (1, 100),
400
- "retry_attempts": (0, 10),
401
- "retry_delay_seconds": (0, 60),
402
- "max_log_size_mb": (1, 100)
403
- }
404
-
405
- if key in numeric_ranges:
406
- min_val, max_val = numeric_ranges[key]
407
- return min_val <= value <= max_val
408
-
409
- # Enum validation for specific settings
410
- if key == "curl_export_format":
411
- return value in ["standard", "minimal", "verbose"]
412
-
413
- if key == "default_body_type":
414
- return value in ["None", "JSON", "Form Data", "Multipart Form", "Raw Text", "Binary"]
415
-
416
- return True
417
-
418
- def _create_backup(self):
419
- """Create a backup of the current settings file."""
420
- try:
421
- if os.path.exists(self.settings_file):
422
- backup_file = f"{self.settings_file}.backup"
423
-
424
- # Read current file
425
- with open(self.settings_file, 'r', encoding='utf-8') as f:
426
- content = f.read()
427
-
428
- # Write backup
429
- with open(backup_file, 'w', encoding='utf-8') as f:
430
- f.write(content)
431
-
432
- self.logger.debug(f"Settings backup created: {backup_file}")
433
-
434
- except Exception as e:
435
- self.logger.warning(f"Could not create settings backup: {e}")
436
-
437
- def restore_from_backup(self) -> bool:
438
- """
439
- Restore settings from backup file.
440
-
441
- Returns:
442
- True if successful, False otherwise
443
- """
444
- try:
445
- backup_file = f"{self.settings_file}.backup"
446
-
447
- if os.path.exists(backup_file):
448
- # Load backup
449
- with open(backup_file, 'r', encoding='utf-8') as f:
450
- backup_settings = json.load(f)
451
-
452
- # Validate backup settings
453
- temp_settings = self.settings.copy()
454
- self.settings = backup_settings
455
- self._validate_settings()
456
-
457
- # Save restored settings
458
- if self.save_settings():
459
- self.logger.info("Settings restored from backup")
460
- return True
461
- else:
462
- # Restore original settings if save failed
463
- self.settings = temp_settings
464
- return False
465
- else:
466
- self.logger.warning("No backup file found")
467
- return False
468
-
469
- except Exception as e:
470
- self.logger.error(f"Error restoring from backup: {e}")
471
- return False
472
-
473
- def cleanup_old_backups(self, max_backups: int = 5):
474
- """
475
- Clean up old backup files.
476
-
477
- Args:
478
- max_backups: Maximum number of backup files to keep
479
- """
480
- try:
481
- backup_pattern = f"{self.settings_file}.backup"
482
- backup_dir = os.path.dirname(self.settings_file) or "."
483
-
484
- # Find all backup files
485
- backup_files = []
486
- for filename in os.listdir(backup_dir):
487
- if filename.startswith(os.path.basename(backup_pattern)):
488
- filepath = os.path.join(backup_dir, filename)
489
- backup_files.append((filepath, os.path.getmtime(filepath)))
490
-
491
- # Sort by modification time (newest first)
492
- backup_files.sort(key=lambda x: x[1], reverse=True)
493
-
494
- # Remove old backups
495
- for filepath, _ in backup_files[max_backups:]:
496
- try:
497
- os.remove(filepath)
498
- self.logger.debug(f"Removed old backup: {filepath}")
499
- except Exception as e:
500
- self.logger.warning(f"Could not remove backup {filepath}: {e}")
501
-
502
- except Exception as e:
1
+ """
2
+ cURL Tool Settings Management Module
3
+
4
+ This module provides settings persistence and management for the cURL GUI Tool.
5
+ It handles configuration storage, loading, and validation for tool settings.
6
+
7
+ Author: Pomera AI Commander
8
+ """
9
+
10
+ import json
11
+ import os
12
+ from typing import Dict, Any, Optional
13
+ import logging
14
+ from datetime import datetime
15
+
16
+
17
+ class CurlSettingsManager:
18
+ """
19
+ Manages settings persistence and configuration for the cURL Tool.
20
+
21
+ Handles:
22
+ - Settings file storage and loading
23
+ - Default configuration values
24
+ - Settings validation
25
+ - Configuration backup and restore
26
+ """
27
+
28
+ def __init__(self, settings_file: str = "settings.json", logger=None):
29
+ """
30
+ Initialize the settings manager.
31
+
32
+ Args:
33
+ settings_file: Path to the main settings file
34
+ logger: Logger instance for debugging
35
+ """
36
+ self.settings_file = settings_file
37
+ self.logger = logger or logging.getLogger(__name__)
38
+ self.tool_key = "cURL Tool" # Key in tool_settings section
39
+
40
+ # Default settings configuration
41
+ self.default_settings = {
42
+ # Request settings
43
+ "default_timeout": 30,
44
+ "follow_redirects": True,
45
+ "verify_ssl": True,
46
+ "max_redirects": 10,
47
+ "user_agent": "Pomera cURL Tool/1.0",
48
+
49
+ # History settings
50
+ "save_history": True,
51
+ "max_history_items": 100,
52
+ "auto_cleanup_history": True,
53
+ "history_retention_days": 30,
54
+
55
+ # Authentication settings
56
+ "persist_auth": True,
57
+ "auth_timeout_minutes": 60,
58
+ "clear_auth_on_exit": False,
59
+
60
+ # UI settings
61
+ "remember_window_size": True,
62
+ "default_body_type": "JSON",
63
+ "auto_format_json": True,
64
+ "syntax_highlighting": True,
65
+ "show_response_time": True,
66
+
67
+ # Download settings
68
+ "default_download_path": "",
69
+ "use_remote_filename": True,
70
+ "resume_downloads": True,
71
+ "download_chunk_size": 8192,
72
+
73
+ # Export/Import settings
74
+ "curl_export_format": "standard", # standard, minimal, verbose
75
+ "include_comments_in_export": True,
76
+ "auto_escape_special_chars": True,
77
+ "complex_options": "", # Additional cURL options not handled by UI
78
+
79
+ # Debug settings
80
+ "enable_debug_logging": False,
81
+ "log_request_headers": True,
82
+ "log_response_headers": True,
83
+ "max_log_size_mb": 10,
84
+
85
+ # Advanced settings
86
+ "connection_pool_size": 10,
87
+ "retry_attempts": 3,
88
+ "retry_delay_seconds": 1,
89
+ "enable_http2": False,
90
+
91
+ # Version and metadata
92
+ "settings_version": "1.0",
93
+ "last_updated": None,
94
+ "created_date": None
95
+ }
96
+
97
+ # Current settings (loaded from file or defaults)
98
+ self.settings = {}
99
+
100
+ # Load settings on initialization
101
+ self.load_settings()
102
+
103
+ def load_settings(self) -> Dict[str, Any]:
104
+ """
105
+ Load settings from centralized settings.json file.
106
+
107
+ Returns:
108
+ Dictionary of current settings
109
+ """
110
+ try:
111
+ if os.path.exists(self.settings_file):
112
+ with open(self.settings_file, 'r', encoding='utf-8') as f:
113
+ all_settings = json.load(f)
114
+
115
+ # Get tool settings from tool_settings section
116
+ tool_settings = all_settings.get("tool_settings", {})
117
+ file_settings = tool_settings.get(self.tool_key, {})
118
+
119
+ # Merge with defaults to ensure all keys exist
120
+ self.settings = self.default_settings.copy()
121
+ self.settings.update(file_settings)
122
+
123
+ # Validate and fix any invalid settings
124
+ self._validate_settings()
125
+
126
+ self.logger.info(f"cURL Tool settings loaded from {self.settings_file}")
127
+ else:
128
+ # Use defaults and create file
129
+ self.settings = self.default_settings.copy()
130
+ self.settings["created_date"] = datetime.now().isoformat()
131
+ self.save_settings()
132
+ self.logger.info("Created new settings file with defaults")
133
+
134
+ except Exception as e:
135
+ self.logger.error(f"Error loading settings: {e}")
136
+ # Fall back to defaults
137
+ self.settings = self.default_settings.copy()
138
+ self.settings["created_date"] = datetime.now().isoformat()
139
+
140
+ return self.settings
141
+
142
+ def save_settings(self) -> bool:
143
+ """
144
+ Save current settings to centralized settings.json file.
145
+
146
+ Returns:
147
+ True if successful, False otherwise
148
+ """
149
+ try:
150
+ # Update last modified timestamp
151
+ self.settings["last_updated"] = datetime.now().isoformat()
152
+
153
+ # Load existing settings file
154
+ all_settings = {}
155
+ if os.path.exists(self.settings_file):
156
+ with open(self.settings_file, 'r', encoding='utf-8') as f:
157
+ all_settings = json.load(f)
158
+
159
+ # Ensure tool_settings section exists
160
+ if "tool_settings" not in all_settings:
161
+ all_settings["tool_settings"] = {}
162
+
163
+ # Update cURL Tool settings (merge with existing data to preserve history)
164
+ if self.tool_key not in all_settings["tool_settings"]:
165
+ all_settings["tool_settings"][self.tool_key] = {}
166
+
167
+ # Preserve existing non-settings data (like history, collections)
168
+ existing_data = all_settings["tool_settings"][self.tool_key]
169
+
170
+ # Update with current settings
171
+ all_settings["tool_settings"][self.tool_key] = {**existing_data, **self.settings}
172
+
173
+ # Create backup of existing settings
174
+ self._create_backup()
175
+
176
+ # Write settings to file
177
+ with open(self.settings_file, 'w', encoding='utf-8') as f:
178
+ json.dump(all_settings, f, indent=4, ensure_ascii=False)
179
+
180
+ self.logger.info(f"cURL Tool settings saved to {self.settings_file}")
181
+ return True
182
+
183
+ except Exception as e:
184
+ self.logger.error(f"Error saving settings: {e}")
185
+ return False
186
+
187
+ def get_setting(self, key: str, default: Any = None) -> Any:
188
+ """
189
+ Get a setting value.
190
+
191
+ Args:
192
+ key: Setting key
193
+ default: Default value if key not found
194
+
195
+ Returns:
196
+ Setting value or default
197
+ """
198
+ return self.settings.get(key, default)
199
+
200
+ def set_setting(self, key: str, value: Any) -> bool:
201
+ """
202
+ Set a setting value.
203
+
204
+ Args:
205
+ key: Setting key
206
+ value: Setting value
207
+
208
+ Returns:
209
+ True if successful, False otherwise
210
+ """
211
+ try:
212
+ # Validate the setting
213
+ if self._validate_setting(key, value):
214
+ self.settings[key] = value
215
+ return True
216
+ else:
217
+ self.logger.warning(f"Invalid setting value: {key} = {value}")
218
+ return False
219
+
220
+ except Exception as e:
221
+ self.logger.error(f"Error setting {key}: {e}")
222
+ return False
223
+
224
+ def reset_to_defaults(self) -> bool:
225
+ """
226
+ Reset all settings to defaults.
227
+
228
+ Returns:
229
+ True if successful, False otherwise
230
+ """
231
+ try:
232
+ # Keep creation date if it exists
233
+ created_date = self.settings.get("created_date")
234
+
235
+ self.settings = self.default_settings.copy()
236
+ if created_date:
237
+ self.settings["created_date"] = created_date
238
+
239
+ return self.save_settings()
240
+
241
+ except Exception as e:
242
+ self.logger.error(f"Error resetting settings: {e}")
243
+ return False
244
+
245
+ def export_settings(self, filepath: str) -> bool:
246
+ """
247
+ Export settings to a file.
248
+
249
+ Args:
250
+ filepath: Path to export file
251
+
252
+ Returns:
253
+ True if successful, False otherwise
254
+ """
255
+ try:
256
+ export_data = {
257
+ "settings": self.settings,
258
+ "export_date": datetime.now().isoformat(),
259
+ "version": self.settings.get("settings_version", "1.0")
260
+ }
261
+
262
+ with open(filepath, 'w', encoding='utf-8') as f:
263
+ json.dump(export_data, f, indent=2, ensure_ascii=False)
264
+
265
+ self.logger.info(f"Settings exported to {filepath}")
266
+ return True
267
+
268
+ except Exception as e:
269
+ self.logger.error(f"Error exporting settings: {e}")
270
+ return False
271
+
272
+ def import_settings(self, filepath: str) -> bool:
273
+ """
274
+ Import settings from a file.
275
+
276
+ Args:
277
+ filepath: Path to import file
278
+
279
+ Returns:
280
+ True if successful, False otherwise
281
+ """
282
+ try:
283
+ with open(filepath, 'r', encoding='utf-8') as f:
284
+ import_data = json.load(f)
285
+
286
+ # Extract settings from import data
287
+ if "settings" in import_data:
288
+ imported_settings = import_data["settings"]
289
+ else:
290
+ # Assume the file contains settings directly
291
+ imported_settings = import_data
292
+
293
+ # Merge with current settings (don't overwrite everything)
294
+ for key, value in imported_settings.items():
295
+ if key in self.default_settings:
296
+ if self._validate_setting(key, value):
297
+ self.settings[key] = value
298
+
299
+ # Save the updated settings
300
+ return self.save_settings()
301
+
302
+ except Exception as e:
303
+ self.logger.error(f"Error importing settings: {e}")
304
+ return False
305
+
306
+ def get_all_settings(self) -> Dict[str, Any]:
307
+ """
308
+ Get all current settings.
309
+
310
+ Returns:
311
+ Dictionary of all settings
312
+ """
313
+ return self.settings.copy()
314
+
315
+ def get_default_settings(self) -> Dict[str, Any]:
316
+ """
317
+ Get default settings.
318
+
319
+ Returns:
320
+ Dictionary of default settings
321
+ """
322
+ return self.default_settings.copy()
323
+
324
+ def _validate_settings(self):
325
+ """Validate and fix invalid settings."""
326
+ # Ensure numeric settings are within valid ranges
327
+ numeric_ranges = {
328
+ "default_timeout": (1, 300),
329
+ "max_redirects": (0, 50),
330
+ "max_history_items": (10, 1000),
331
+ "history_retention_days": (1, 365),
332
+ "auth_timeout_minutes": (5, 1440),
333
+ "download_chunk_size": (1024, 1048576),
334
+ "connection_pool_size": (1, 100),
335
+ "retry_attempts": (0, 10),
336
+ "retry_delay_seconds": (0, 60),
337
+ "max_log_size_mb": (1, 100)
338
+ }
339
+
340
+ for key, (min_val, max_val) in numeric_ranges.items():
341
+ if key in self.settings:
342
+ value = self.settings[key]
343
+ if not isinstance(value, (int, float)) or value < min_val or value > max_val:
344
+ self.settings[key] = self.default_settings[key]
345
+ self.logger.warning(f"Reset invalid setting {key} to default")
346
+
347
+ # Ensure string settings are not empty where required
348
+ required_strings = ["user_agent", "curl_export_format", "settings_version"]
349
+ for key in required_strings:
350
+ if key in self.settings and not isinstance(self.settings[key], str):
351
+ self.settings[key] = self.default_settings[key]
352
+ self.logger.warning(f"Reset invalid string setting {key} to default")
353
+
354
+ # Ensure boolean settings are actually boolean
355
+ boolean_settings = [
356
+ "follow_redirects", "verify_ssl", "save_history", "auto_cleanup_history",
357
+ "persist_auth", "clear_auth_on_exit", "remember_window_size",
358
+ "auto_format_json", "syntax_highlighting", "show_response_time",
359
+ "use_remote_filename", "resume_downloads", "include_comments_in_export",
360
+ "auto_escape_special_chars", "enable_debug_logging", "log_request_headers",
361
+ "log_response_headers", "enable_http2"
362
+ ]
363
+
364
+ for key in boolean_settings:
365
+ if key in self.settings and not isinstance(self.settings[key], bool):
366
+ self.settings[key] = self.default_settings[key]
367
+ self.logger.warning(f"Reset invalid boolean setting {key} to default")
368
+
369
+ def _validate_setting(self, key: str, value: Any) -> bool:
370
+ """
371
+ Validate a single setting value.
372
+
373
+ Args:
374
+ key: Setting key
375
+ value: Setting value
376
+
377
+ Returns:
378
+ True if valid, False otherwise
379
+ """
380
+ if key not in self.default_settings:
381
+ return False
382
+
383
+ # Type validation
384
+ expected_type = type(self.default_settings[key])
385
+ if not isinstance(value, expected_type):
386
+ # Allow None for optional settings
387
+ if value is None and key in ["default_download_path", "last_updated", "created_date"]:
388
+ return True
389
+ return False
390
+
391
+ # Range validation for numeric values
392
+ numeric_ranges = {
393
+ "default_timeout": (1, 300),
394
+ "max_redirects": (0, 50),
395
+ "max_history_items": (10, 1000),
396
+ "history_retention_days": (1, 365),
397
+ "auth_timeout_minutes": (5, 1440),
398
+ "download_chunk_size": (1024, 1048576),
399
+ "connection_pool_size": (1, 100),
400
+ "retry_attempts": (0, 10),
401
+ "retry_delay_seconds": (0, 60),
402
+ "max_log_size_mb": (1, 100)
403
+ }
404
+
405
+ if key in numeric_ranges:
406
+ min_val, max_val = numeric_ranges[key]
407
+ return min_val <= value <= max_val
408
+
409
+ # Enum validation for specific settings
410
+ if key == "curl_export_format":
411
+ return value in ["standard", "minimal", "verbose"]
412
+
413
+ if key == "default_body_type":
414
+ return value in ["None", "JSON", "Form Data", "Multipart Form", "Raw Text", "Binary"]
415
+
416
+ return True
417
+
418
+ def _create_backup(self):
419
+ """Create a backup of the current settings file."""
420
+ try:
421
+ if os.path.exists(self.settings_file):
422
+ backup_file = f"{self.settings_file}.backup"
423
+
424
+ # Read current file
425
+ with open(self.settings_file, 'r', encoding='utf-8') as f:
426
+ content = f.read()
427
+
428
+ # Write backup
429
+ with open(backup_file, 'w', encoding='utf-8') as f:
430
+ f.write(content)
431
+
432
+ self.logger.debug(f"Settings backup created: {backup_file}")
433
+
434
+ except Exception as e:
435
+ self.logger.warning(f"Could not create settings backup: {e}")
436
+
437
+ def restore_from_backup(self) -> bool:
438
+ """
439
+ Restore settings from backup file.
440
+
441
+ Returns:
442
+ True if successful, False otherwise
443
+ """
444
+ try:
445
+ backup_file = f"{self.settings_file}.backup"
446
+
447
+ if os.path.exists(backup_file):
448
+ # Load backup
449
+ with open(backup_file, 'r', encoding='utf-8') as f:
450
+ backup_settings = json.load(f)
451
+
452
+ # Validate backup settings
453
+ temp_settings = self.settings.copy()
454
+ self.settings = backup_settings
455
+ self._validate_settings()
456
+
457
+ # Save restored settings
458
+ if self.save_settings():
459
+ self.logger.info("Settings restored from backup")
460
+ return True
461
+ else:
462
+ # Restore original settings if save failed
463
+ self.settings = temp_settings
464
+ return False
465
+ else:
466
+ self.logger.warning("No backup file found")
467
+ return False
468
+
469
+ except Exception as e:
470
+ self.logger.error(f"Error restoring from backup: {e}")
471
+ return False
472
+
473
+ def cleanup_old_backups(self, max_backups: int = 5):
474
+ """
475
+ Clean up old backup files.
476
+
477
+ Args:
478
+ max_backups: Maximum number of backup files to keep
479
+ """
480
+ try:
481
+ backup_pattern = f"{self.settings_file}.backup"
482
+ backup_dir = os.path.dirname(self.settings_file) or "."
483
+
484
+ # Find all backup files
485
+ backup_files = []
486
+ for filename in os.listdir(backup_dir):
487
+ if filename.startswith(os.path.basename(backup_pattern)):
488
+ filepath = os.path.join(backup_dir, filename)
489
+ backup_files.append((filepath, os.path.getmtime(filepath)))
490
+
491
+ # Sort by modification time (newest first)
492
+ backup_files.sort(key=lambda x: x[1], reverse=True)
493
+
494
+ # Remove old backups
495
+ for filepath, _ in backup_files[max_backups:]:
496
+ try:
497
+ os.remove(filepath)
498
+ self.logger.debug(f"Removed old backup: {filepath}")
499
+ except Exception as e:
500
+ self.logger.warning(f"Could not remove backup {filepath}: {e}")
501
+
502
+ except Exception as e:
503
503
  self.logger.warning(f"Error cleaning up backups: {e}")