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