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,601 +1,601 @@
1
- """
2
- cURL Tool History Management Module
3
-
4
- This module provides request history management for the cURL GUI Tool.
5
- It handles history storage, persistence, and organization functionality.
6
-
7
- Author: Pomera AI Commander
8
- """
9
-
10
- import json
11
- import os
12
- from typing import Dict, Any, Optional, List
13
- import logging
14
- from datetime import datetime, timedelta
15
- from dataclasses import dataclass, asdict
16
-
17
-
18
- @dataclass
19
- class RequestHistoryItem:
20
- """Individual request history entry."""
21
- timestamp: str
22
- method: str
23
- url: str
24
- status_code: Optional[int]
25
- response_time: Optional[float]
26
- success: bool
27
- headers: Dict[str, str]
28
- body: Optional[str]
29
- auth_type: str
30
- response_preview: str # First 200 chars of response
31
- response_size: Optional[int]
32
- content_type: Optional[str]
33
-
34
- def to_dict(self) -> Dict[str, Any]:
35
- """Convert to dictionary for JSON serialization."""
36
- return asdict(self)
37
-
38
- @classmethod
39
- def from_dict(cls, data: Dict[str, Any]) -> 'RequestHistoryItem':
40
- """Create from dictionary."""
41
- return cls(**data)
42
-
43
-
44
- class CurlHistoryManager:
45
- """
46
- Manages request history persistence and organization for the cURL Tool.
47
-
48
- Handles:
49
- - History file storage and loading
50
- - History item management (add, remove, search)
51
- - History cleanup and organization
52
- - Collections support
53
- """
54
-
55
- def __init__(self, history_file: str = "settings.json",
56
- max_items: int = 100, logger=None):
57
- """
58
- Initialize the history manager.
59
-
60
- Args:
61
- history_file: Path to the main settings file
62
- max_items: Maximum number of history items to keep
63
- logger: Logger instance for debugging
64
- """
65
- self.history_file = history_file
66
- self.max_items = max_items
67
- self.logger = logger or logging.getLogger(__name__)
68
- self.tool_key = "cURL Tool" # Key in tool_settings section
69
-
70
- # History storage
71
- self.history: List[RequestHistoryItem] = []
72
- self.collections: Dict[str, List[str]] = {} # Collection name -> list of history item IDs
73
-
74
- # Load history on initialization
75
- self.load_history()
76
-
77
- def add_request(self, method: str, url: str, headers: Dict[str, str] = None,
78
- body: str = None, auth_type: str = "None",
79
- status_code: int = None, response_time: float = None,
80
- success: bool = True, response_body: str = "",
81
- response_size: int = None, content_type: str = None) -> str:
82
- """
83
- Add a request to history.
84
-
85
- Args:
86
- method: HTTP method
87
- url: Request URL
88
- headers: Request headers
89
- body: Request body
90
- auth_type: Authentication type used
91
- status_code: Response status code
92
- response_time: Response time in seconds
93
- success: Whether request was successful
94
- response_body: Response body content
95
- response_size: Response size in bytes
96
- content_type: Response content type
97
-
98
- Returns:
99
- History item ID (timestamp)
100
- """
101
- timestamp = datetime.now().isoformat()
102
-
103
- # Create response preview (first 200 chars)
104
- response_preview = ""
105
- if response_body:
106
- response_preview = response_body[:200]
107
- if len(response_body) > 200:
108
- response_preview += "..."
109
-
110
- # Create history item
111
- history_item = RequestHistoryItem(
112
- timestamp=timestamp,
113
- method=method,
114
- url=url,
115
- status_code=status_code,
116
- response_time=response_time,
117
- success=success,
118
- headers=headers or {},
119
- body=body,
120
- auth_type=auth_type,
121
- response_preview=response_preview,
122
- response_size=response_size,
123
- content_type=content_type
124
- )
125
-
126
- # Add to history
127
- self.history.append(history_item)
128
-
129
- # Maintain max items limit
130
- if len(self.history) > self.max_items:
131
- removed_item = self.history.pop(0)
132
- # Remove from collections if present
133
- self._remove_from_collections(removed_item.timestamp)
134
-
135
- # Save history
136
- self.save_history()
137
-
138
- self.logger.debug(f"Added request to history: {method} {url}")
139
- return timestamp
140
-
141
- def get_history(self, limit: int = None) -> List[RequestHistoryItem]:
142
- """
143
- Get history items.
144
-
145
- Args:
146
- limit: Maximum number of items to return
147
-
148
- Returns:
149
- List of history items (newest first)
150
- """
151
- history = sorted(self.history, key=lambda x: x.timestamp, reverse=True)
152
- if limit:
153
- return history[:limit]
154
- return history
155
-
156
- def get_history_item(self, timestamp: str) -> Optional[RequestHistoryItem]:
157
- """
158
- Get a specific history item by timestamp.
159
-
160
- Args:
161
- timestamp: Item timestamp ID
162
-
163
- Returns:
164
- History item or None if not found
165
- """
166
- for item in self.history:
167
- if item.timestamp == timestamp:
168
- return item
169
- return None
170
-
171
- def remove_history_item(self, timestamp: str) -> bool:
172
- """
173
- Remove a history item.
174
-
175
- Args:
176
- timestamp: Item timestamp ID
177
-
178
- Returns:
179
- True if removed, False if not found
180
- """
181
- for i, item in enumerate(self.history):
182
- if item.timestamp == timestamp:
183
- self.history.pop(i)
184
- self._remove_from_collections(timestamp)
185
- self.save_history()
186
- self.logger.debug(f"Removed history item: {timestamp}")
187
- return True
188
- return False
189
-
190
- def clear_history(self) -> bool:
191
- """
192
- Clear all history.
193
-
194
- Returns:
195
- True if successful
196
- """
197
- try:
198
- self.history.clear()
199
- self.collections.clear()
200
- self.save_history()
201
- self.logger.info("History cleared")
202
- return True
203
- except Exception as e:
204
- self.logger.error(f"Error clearing history: {e}")
205
- return False
206
-
207
- def search_history(self, query: str, field: str = "all") -> List[RequestHistoryItem]:
208
- """
209
- Search history items.
210
-
211
- Args:
212
- query: Search query
213
- field: Field to search in ("all", "url", "method", "status")
214
-
215
- Returns:
216
- List of matching history items
217
- """
218
- query = query.lower()
219
- results = []
220
-
221
- for item in self.history:
222
- match = False
223
-
224
- if field == "all":
225
- # Search in multiple fields
226
- search_text = f"{item.method} {item.url} {item.status_code or ''} {item.response_preview}".lower()
227
- match = query in search_text
228
- elif field == "url":
229
- match = query in item.url.lower()
230
- elif field == "method":
231
- match = query in item.method.lower()
232
- elif field == "status":
233
- match = query in str(item.status_code or "")
234
-
235
- if match:
236
- results.append(item)
237
-
238
- return sorted(results, key=lambda x: x.timestamp, reverse=True)
239
-
240
- def cleanup_old_history(self, retention_days: int = 30) -> int:
241
- """
242
- Clean up old history items.
243
-
244
- Args:
245
- retention_days: Number of days to retain history
246
-
247
- Returns:
248
- Number of items removed
249
- """
250
- try:
251
- cutoff_date = datetime.now() - timedelta(days=retention_days)
252
- initial_count = len(self.history)
253
-
254
- # Filter out old items
255
- self.history = [
256
- item for item in self.history
257
- if datetime.fromisoformat(item.timestamp) > cutoff_date
258
- ]
259
-
260
- removed_count = initial_count - len(self.history)
261
-
262
- if removed_count > 0:
263
- # Clean up collections
264
- self._cleanup_collections()
265
- self.save_history()
266
- self.logger.info(f"Cleaned up {removed_count} old history items")
267
-
268
- return removed_count
269
-
270
- except Exception as e:
271
- self.logger.error(f"Error cleaning up history: {e}")
272
- return 0
273
-
274
- def create_collection(self, name: str, item_timestamps: List[str] = None) -> bool:
275
- """
276
- Create a new collection.
277
-
278
- Args:
279
- name: Collection name
280
- item_timestamps: List of history item timestamps to include
281
-
282
- Returns:
283
- True if successful
284
- """
285
- try:
286
- if name in self.collections:
287
- self.logger.warning(f"Collection '{name}' already exists")
288
- return False
289
-
290
- self.collections[name] = item_timestamps or []
291
- self.save_history()
292
- self.logger.info(f"Created collection: {name}")
293
- return True
294
-
295
- except Exception as e:
296
- self.logger.error(f"Error creating collection: {e}")
297
- return False
298
-
299
- def add_to_collection(self, collection_name: str, timestamp: str) -> bool:
300
- """
301
- Add a history item to a collection.
302
-
303
- Args:
304
- collection_name: Name of the collection
305
- timestamp: History item timestamp
306
-
307
- Returns:
308
- True if successful
309
- """
310
- try:
311
- if collection_name not in self.collections:
312
- self.collections[collection_name] = []
313
-
314
- if timestamp not in self.collections[collection_name]:
315
- # Verify the history item exists
316
- if self.get_history_item(timestamp):
317
- self.collections[collection_name].append(timestamp)
318
- self.save_history()
319
- self.logger.debug(f"Added item {timestamp} to collection {collection_name}")
320
- return True
321
- else:
322
- self.logger.warning(f"History item {timestamp} not found")
323
- return False
324
- else:
325
- self.logger.debug(f"Item {timestamp} already in collection {collection_name}")
326
- return True
327
-
328
- except Exception as e:
329
- self.logger.error(f"Error adding to collection: {e}")
330
- return False
331
-
332
- def remove_from_collection(self, collection_name: str, timestamp: str) -> bool:
333
- """
334
- Remove a history item from a collection.
335
-
336
- Args:
337
- collection_name: Name of the collection
338
- timestamp: History item timestamp
339
-
340
- Returns:
341
- True if successful
342
- """
343
- try:
344
- if collection_name in self.collections:
345
- if timestamp in self.collections[collection_name]:
346
- self.collections[collection_name].remove(timestamp)
347
- self.save_history()
348
- self.logger.debug(f"Removed item {timestamp} from collection {collection_name}")
349
- return True
350
- return False
351
-
352
- except Exception as e:
353
- self.logger.error(f"Error removing from collection: {e}")
354
- return False
355
-
356
- def get_collection(self, name: str) -> List[RequestHistoryItem]:
357
- """
358
- Get items in a collection.
359
-
360
- Args:
361
- name: Collection name
362
-
363
- Returns:
364
- List of history items in the collection
365
- """
366
- if name not in self.collections:
367
- return []
368
-
369
- items = []
370
- for timestamp in self.collections[name]:
371
- item = self.get_history_item(timestamp)
372
- if item:
373
- items.append(item)
374
-
375
- return sorted(items, key=lambda x: x.timestamp, reverse=True)
376
-
377
- def get_collections(self) -> Dict[str, int]:
378
- """
379
- Get all collections with item counts.
380
-
381
- Returns:
382
- Dictionary of collection name -> item count
383
- """
384
- result = {}
385
- for name, timestamps in self.collections.items():
386
- # Count only existing items
387
- count = sum(1 for ts in timestamps if self.get_history_item(ts))
388
- result[name] = count
389
- return result
390
-
391
- def delete_collection(self, name: str) -> bool:
392
- """
393
- Delete a collection.
394
-
395
- Args:
396
- name: Collection name
397
-
398
- Returns:
399
- True if successful
400
- """
401
- try:
402
- if name in self.collections:
403
- del self.collections[name]
404
- self.save_history()
405
- self.logger.info(f"Deleted collection: {name}")
406
- return True
407
- return False
408
-
409
- except Exception as e:
410
- self.logger.error(f"Error deleting collection: {e}")
411
- return False
412
-
413
- def export_history(self, filepath: str, collection_name: str = None) -> bool:
414
- """
415
- Export history to a file.
416
-
417
- Args:
418
- filepath: Export file path
419
- collection_name: Optional collection name to export
420
-
421
- Returns:
422
- True if successful
423
- """
424
- try:
425
- if collection_name:
426
- items = self.get_collection(collection_name)
427
- export_data = {
428
- "type": "collection",
429
- "name": collection_name,
430
- "items": [item.to_dict() for item in items]
431
- }
432
- else:
433
- export_data = {
434
- "type": "full_history",
435
- "items": [item.to_dict() for item in self.history],
436
- "collections": self.collections
437
- }
438
-
439
- export_data["export_date"] = datetime.now().isoformat()
440
- export_data["version"] = "1.0"
441
-
442
- with open(filepath, 'w', encoding='utf-8') as f:
443
- json.dump(export_data, f, indent=2, ensure_ascii=False)
444
-
445
- self.logger.info(f"History exported to {filepath}")
446
- return True
447
-
448
- except Exception as e:
449
- self.logger.error(f"Error exporting history: {e}")
450
- return False
451
-
452
- def import_history(self, filepath: str, merge: bool = True) -> bool:
453
- """
454
- Import history from a file.
455
-
456
- Args:
457
- filepath: Import file path
458
- merge: Whether to merge with existing history
459
-
460
- Returns:
461
- True if successful
462
- """
463
- try:
464
- with open(filepath, 'r', encoding='utf-8') as f:
465
- import_data = json.load(f)
466
-
467
- if not merge:
468
- self.history.clear()
469
- self.collections.clear()
470
-
471
- # Import items
472
- items_data = import_data.get("items", [])
473
- for item_data in items_data:
474
- try:
475
- item = RequestHistoryItem.from_dict(item_data)
476
- # Check if item already exists (by timestamp)
477
- if not any(h.timestamp == item.timestamp for h in self.history):
478
- self.history.append(item)
479
- except Exception as e:
480
- self.logger.warning(f"Error importing history item: {e}")
481
-
482
- # Import collections if present
483
- if "collections" in import_data:
484
- for name, timestamps in import_data["collections"].items():
485
- if name not in self.collections:
486
- self.collections[name] = []
487
- # Add timestamps that don't already exist
488
- for ts in timestamps:
489
- if ts not in self.collections[name]:
490
- self.collections[name].append(ts)
491
-
492
- # Maintain max items limit
493
- if len(self.history) > self.max_items:
494
- # Sort by timestamp and keep newest
495
- self.history.sort(key=lambda x: x.timestamp, reverse=True)
496
- self.history = self.history[:self.max_items]
497
-
498
- self.save_history()
499
- self.logger.info(f"History imported from {filepath}")
500
- return True
501
-
502
- except Exception as e:
503
- self.logger.error(f"Error importing history: {e}")
504
- return False
505
-
506
- def load_history(self) -> bool:
507
- """
508
- Load history from centralized settings.json file.
509
-
510
- Returns:
511
- True if successful
512
- """
513
- try:
514
- if os.path.exists(self.history_file):
515
- with open(self.history_file, 'r', encoding='utf-8') as f:
516
- all_settings = json.load(f)
517
-
518
- # Get tool settings from tool_settings section
519
- tool_settings = all_settings.get("tool_settings", {})
520
- curl_settings = tool_settings.get(self.tool_key, {})
521
-
522
- # Load history items
523
- self.history = []
524
- for item_data in curl_settings.get("history", []):
525
- try:
526
- item = RequestHistoryItem.from_dict(item_data)
527
- self.history.append(item)
528
- except Exception as e:
529
- self.logger.warning(f"Error loading history item: {e}")
530
-
531
- # Load collections
532
- self.collections = curl_settings.get("collections", {})
533
-
534
- self.logger.info(f"Loaded {len(self.history)} history items from {self.history_file}")
535
- return True
536
- else:
537
- self.logger.info("No history file found, starting with empty history")
538
- return True
539
-
540
- except Exception as e:
541
- self.logger.error(f"Error loading history: {e}")
542
- return False
543
-
544
- def save_history(self) -> bool:
545
- """
546
- Save history to centralized settings.json file.
547
-
548
- Returns:
549
- True if successful
550
- """
551
- try:
552
- # Load existing settings file
553
- all_settings = {}
554
- if os.path.exists(self.history_file):
555
- with open(self.history_file, 'r', encoding='utf-8') as f:
556
- all_settings = json.load(f)
557
-
558
- # Ensure tool_settings section exists
559
- if "tool_settings" not in all_settings:
560
- all_settings["tool_settings"] = {}
561
-
562
- # Ensure cURL Tool section exists
563
- if self.tool_key not in all_settings["tool_settings"]:
564
- all_settings["tool_settings"][self.tool_key] = {}
565
-
566
- # Update history and collections in cURL Tool settings
567
- all_settings["tool_settings"][self.tool_key]["history"] = [item.to_dict() for item in self.history]
568
- all_settings["tool_settings"][self.tool_key]["collections"] = self.collections
569
- all_settings["tool_settings"][self.tool_key]["history_last_updated"] = datetime.now().isoformat()
570
- all_settings["tool_settings"][self.tool_key]["history_version"] = "1.0"
571
-
572
- # Write settings to file
573
- with open(self.history_file, 'w', encoding='utf-8') as f:
574
- json.dump(all_settings, f, indent=4, ensure_ascii=False)
575
-
576
- self.logger.debug(f"History saved to {self.history_file}")
577
- return True
578
-
579
- except Exception as e:
580
- self.logger.error(f"Error saving history: {e}")
581
- return False
582
-
583
- def _remove_from_collections(self, timestamp: str):
584
- """Remove a timestamp from all collections."""
585
- for collection_name in list(self.collections.keys()):
586
- if timestamp in self.collections[collection_name]:
587
- self.collections[collection_name].remove(timestamp)
588
-
589
- def _cleanup_collections(self):
590
- """Clean up collections by removing references to non-existent items."""
591
- for collection_name in list(self.collections.keys()):
592
- # Filter out timestamps that don't exist in history
593
- valid_timestamps = [
594
- ts for ts in self.collections[collection_name]
595
- if self.get_history_item(ts) is not None
596
- ]
597
- self.collections[collection_name] = valid_timestamps
598
-
599
- # Remove empty collections
600
- if not self.collections[collection_name]:
1
+ """
2
+ cURL Tool History Management Module
3
+
4
+ This module provides request history management for the cURL GUI Tool.
5
+ It handles history storage, persistence, and organization functionality.
6
+
7
+ Author: Pomera AI Commander
8
+ """
9
+
10
+ import json
11
+ import os
12
+ from typing import Dict, Any, Optional, List
13
+ import logging
14
+ from datetime import datetime, timedelta
15
+ from dataclasses import dataclass, asdict
16
+
17
+
18
+ @dataclass
19
+ class RequestHistoryItem:
20
+ """Individual request history entry."""
21
+ timestamp: str
22
+ method: str
23
+ url: str
24
+ status_code: Optional[int]
25
+ response_time: Optional[float]
26
+ success: bool
27
+ headers: Dict[str, str]
28
+ body: Optional[str]
29
+ auth_type: str
30
+ response_preview: str # First 200 chars of response
31
+ response_size: Optional[int]
32
+ content_type: Optional[str]
33
+
34
+ def to_dict(self) -> Dict[str, Any]:
35
+ """Convert to dictionary for JSON serialization."""
36
+ return asdict(self)
37
+
38
+ @classmethod
39
+ def from_dict(cls, data: Dict[str, Any]) -> 'RequestHistoryItem':
40
+ """Create from dictionary."""
41
+ return cls(**data)
42
+
43
+
44
+ class CurlHistoryManager:
45
+ """
46
+ Manages request history persistence and organization for the cURL Tool.
47
+
48
+ Handles:
49
+ - History file storage and loading
50
+ - History item management (add, remove, search)
51
+ - History cleanup and organization
52
+ - Collections support
53
+ """
54
+
55
+ def __init__(self, history_file: str = "settings.json",
56
+ max_items: int = 100, logger=None):
57
+ """
58
+ Initialize the history manager.
59
+
60
+ Args:
61
+ history_file: Path to the main settings file
62
+ max_items: Maximum number of history items to keep
63
+ logger: Logger instance for debugging
64
+ """
65
+ self.history_file = history_file
66
+ self.max_items = max_items
67
+ self.logger = logger or logging.getLogger(__name__)
68
+ self.tool_key = "cURL Tool" # Key in tool_settings section
69
+
70
+ # History storage
71
+ self.history: List[RequestHistoryItem] = []
72
+ self.collections: Dict[str, List[str]] = {} # Collection name -> list of history item IDs
73
+
74
+ # Load history on initialization
75
+ self.load_history()
76
+
77
+ def add_request(self, method: str, url: str, headers: Dict[str, str] = None,
78
+ body: str = None, auth_type: str = "None",
79
+ status_code: int = None, response_time: float = None,
80
+ success: bool = True, response_body: str = "",
81
+ response_size: int = None, content_type: str = None) -> str:
82
+ """
83
+ Add a request to history.
84
+
85
+ Args:
86
+ method: HTTP method
87
+ url: Request URL
88
+ headers: Request headers
89
+ body: Request body
90
+ auth_type: Authentication type used
91
+ status_code: Response status code
92
+ response_time: Response time in seconds
93
+ success: Whether request was successful
94
+ response_body: Response body content
95
+ response_size: Response size in bytes
96
+ content_type: Response content type
97
+
98
+ Returns:
99
+ History item ID (timestamp)
100
+ """
101
+ timestamp = datetime.now().isoformat()
102
+
103
+ # Create response preview (first 200 chars)
104
+ response_preview = ""
105
+ if response_body:
106
+ response_preview = response_body[:200]
107
+ if len(response_body) > 200:
108
+ response_preview += "..."
109
+
110
+ # Create history item
111
+ history_item = RequestHistoryItem(
112
+ timestamp=timestamp,
113
+ method=method,
114
+ url=url,
115
+ status_code=status_code,
116
+ response_time=response_time,
117
+ success=success,
118
+ headers=headers or {},
119
+ body=body,
120
+ auth_type=auth_type,
121
+ response_preview=response_preview,
122
+ response_size=response_size,
123
+ content_type=content_type
124
+ )
125
+
126
+ # Add to history
127
+ self.history.append(history_item)
128
+
129
+ # Maintain max items limit
130
+ if len(self.history) > self.max_items:
131
+ removed_item = self.history.pop(0)
132
+ # Remove from collections if present
133
+ self._remove_from_collections(removed_item.timestamp)
134
+
135
+ # Save history
136
+ self.save_history()
137
+
138
+ self.logger.debug(f"Added request to history: {method} {url}")
139
+ return timestamp
140
+
141
+ def get_history(self, limit: int = None) -> List[RequestHistoryItem]:
142
+ """
143
+ Get history items.
144
+
145
+ Args:
146
+ limit: Maximum number of items to return
147
+
148
+ Returns:
149
+ List of history items (newest first)
150
+ """
151
+ history = sorted(self.history, key=lambda x: x.timestamp, reverse=True)
152
+ if limit:
153
+ return history[:limit]
154
+ return history
155
+
156
+ def get_history_item(self, timestamp: str) -> Optional[RequestHistoryItem]:
157
+ """
158
+ Get a specific history item by timestamp.
159
+
160
+ Args:
161
+ timestamp: Item timestamp ID
162
+
163
+ Returns:
164
+ History item or None if not found
165
+ """
166
+ for item in self.history:
167
+ if item.timestamp == timestamp:
168
+ return item
169
+ return None
170
+
171
+ def remove_history_item(self, timestamp: str) -> bool:
172
+ """
173
+ Remove a history item.
174
+
175
+ Args:
176
+ timestamp: Item timestamp ID
177
+
178
+ Returns:
179
+ True if removed, False if not found
180
+ """
181
+ for i, item in enumerate(self.history):
182
+ if item.timestamp == timestamp:
183
+ self.history.pop(i)
184
+ self._remove_from_collections(timestamp)
185
+ self.save_history()
186
+ self.logger.debug(f"Removed history item: {timestamp}")
187
+ return True
188
+ return False
189
+
190
+ def clear_history(self) -> bool:
191
+ """
192
+ Clear all history.
193
+
194
+ Returns:
195
+ True if successful
196
+ """
197
+ try:
198
+ self.history.clear()
199
+ self.collections.clear()
200
+ self.save_history()
201
+ self.logger.info("History cleared")
202
+ return True
203
+ except Exception as e:
204
+ self.logger.error(f"Error clearing history: {e}")
205
+ return False
206
+
207
+ def search_history(self, query: str, field: str = "all") -> List[RequestHistoryItem]:
208
+ """
209
+ Search history items.
210
+
211
+ Args:
212
+ query: Search query
213
+ field: Field to search in ("all", "url", "method", "status")
214
+
215
+ Returns:
216
+ List of matching history items
217
+ """
218
+ query = query.lower()
219
+ results = []
220
+
221
+ for item in self.history:
222
+ match = False
223
+
224
+ if field == "all":
225
+ # Search in multiple fields
226
+ search_text = f"{item.method} {item.url} {item.status_code or ''} {item.response_preview}".lower()
227
+ match = query in search_text
228
+ elif field == "url":
229
+ match = query in item.url.lower()
230
+ elif field == "method":
231
+ match = query in item.method.lower()
232
+ elif field == "status":
233
+ match = query in str(item.status_code or "")
234
+
235
+ if match:
236
+ results.append(item)
237
+
238
+ return sorted(results, key=lambda x: x.timestamp, reverse=True)
239
+
240
+ def cleanup_old_history(self, retention_days: int = 30) -> int:
241
+ """
242
+ Clean up old history items.
243
+
244
+ Args:
245
+ retention_days: Number of days to retain history
246
+
247
+ Returns:
248
+ Number of items removed
249
+ """
250
+ try:
251
+ cutoff_date = datetime.now() - timedelta(days=retention_days)
252
+ initial_count = len(self.history)
253
+
254
+ # Filter out old items
255
+ self.history = [
256
+ item for item in self.history
257
+ if datetime.fromisoformat(item.timestamp) > cutoff_date
258
+ ]
259
+
260
+ removed_count = initial_count - len(self.history)
261
+
262
+ if removed_count > 0:
263
+ # Clean up collections
264
+ self._cleanup_collections()
265
+ self.save_history()
266
+ self.logger.info(f"Cleaned up {removed_count} old history items")
267
+
268
+ return removed_count
269
+
270
+ except Exception as e:
271
+ self.logger.error(f"Error cleaning up history: {e}")
272
+ return 0
273
+
274
+ def create_collection(self, name: str, item_timestamps: List[str] = None) -> bool:
275
+ """
276
+ Create a new collection.
277
+
278
+ Args:
279
+ name: Collection name
280
+ item_timestamps: List of history item timestamps to include
281
+
282
+ Returns:
283
+ True if successful
284
+ """
285
+ try:
286
+ if name in self.collections:
287
+ self.logger.warning(f"Collection '{name}' already exists")
288
+ return False
289
+
290
+ self.collections[name] = item_timestamps or []
291
+ self.save_history()
292
+ self.logger.info(f"Created collection: {name}")
293
+ return True
294
+
295
+ except Exception as e:
296
+ self.logger.error(f"Error creating collection: {e}")
297
+ return False
298
+
299
+ def add_to_collection(self, collection_name: str, timestamp: str) -> bool:
300
+ """
301
+ Add a history item to a collection.
302
+
303
+ Args:
304
+ collection_name: Name of the collection
305
+ timestamp: History item timestamp
306
+
307
+ Returns:
308
+ True if successful
309
+ """
310
+ try:
311
+ if collection_name not in self.collections:
312
+ self.collections[collection_name] = []
313
+
314
+ if timestamp not in self.collections[collection_name]:
315
+ # Verify the history item exists
316
+ if self.get_history_item(timestamp):
317
+ self.collections[collection_name].append(timestamp)
318
+ self.save_history()
319
+ self.logger.debug(f"Added item {timestamp} to collection {collection_name}")
320
+ return True
321
+ else:
322
+ self.logger.warning(f"History item {timestamp} not found")
323
+ return False
324
+ else:
325
+ self.logger.debug(f"Item {timestamp} already in collection {collection_name}")
326
+ return True
327
+
328
+ except Exception as e:
329
+ self.logger.error(f"Error adding to collection: {e}")
330
+ return False
331
+
332
+ def remove_from_collection(self, collection_name: str, timestamp: str) -> bool:
333
+ """
334
+ Remove a history item from a collection.
335
+
336
+ Args:
337
+ collection_name: Name of the collection
338
+ timestamp: History item timestamp
339
+
340
+ Returns:
341
+ True if successful
342
+ """
343
+ try:
344
+ if collection_name in self.collections:
345
+ if timestamp in self.collections[collection_name]:
346
+ self.collections[collection_name].remove(timestamp)
347
+ self.save_history()
348
+ self.logger.debug(f"Removed item {timestamp} from collection {collection_name}")
349
+ return True
350
+ return False
351
+
352
+ except Exception as e:
353
+ self.logger.error(f"Error removing from collection: {e}")
354
+ return False
355
+
356
+ def get_collection(self, name: str) -> List[RequestHistoryItem]:
357
+ """
358
+ Get items in a collection.
359
+
360
+ Args:
361
+ name: Collection name
362
+
363
+ Returns:
364
+ List of history items in the collection
365
+ """
366
+ if name not in self.collections:
367
+ return []
368
+
369
+ items = []
370
+ for timestamp in self.collections[name]:
371
+ item = self.get_history_item(timestamp)
372
+ if item:
373
+ items.append(item)
374
+
375
+ return sorted(items, key=lambda x: x.timestamp, reverse=True)
376
+
377
+ def get_collections(self) -> Dict[str, int]:
378
+ """
379
+ Get all collections with item counts.
380
+
381
+ Returns:
382
+ Dictionary of collection name -> item count
383
+ """
384
+ result = {}
385
+ for name, timestamps in self.collections.items():
386
+ # Count only existing items
387
+ count = sum(1 for ts in timestamps if self.get_history_item(ts))
388
+ result[name] = count
389
+ return result
390
+
391
+ def delete_collection(self, name: str) -> bool:
392
+ """
393
+ Delete a collection.
394
+
395
+ Args:
396
+ name: Collection name
397
+
398
+ Returns:
399
+ True if successful
400
+ """
401
+ try:
402
+ if name in self.collections:
403
+ del self.collections[name]
404
+ self.save_history()
405
+ self.logger.info(f"Deleted collection: {name}")
406
+ return True
407
+ return False
408
+
409
+ except Exception as e:
410
+ self.logger.error(f"Error deleting collection: {e}")
411
+ return False
412
+
413
+ def export_history(self, filepath: str, collection_name: str = None) -> bool:
414
+ """
415
+ Export history to a file.
416
+
417
+ Args:
418
+ filepath: Export file path
419
+ collection_name: Optional collection name to export
420
+
421
+ Returns:
422
+ True if successful
423
+ """
424
+ try:
425
+ if collection_name:
426
+ items = self.get_collection(collection_name)
427
+ export_data = {
428
+ "type": "collection",
429
+ "name": collection_name,
430
+ "items": [item.to_dict() for item in items]
431
+ }
432
+ else:
433
+ export_data = {
434
+ "type": "full_history",
435
+ "items": [item.to_dict() for item in self.history],
436
+ "collections": self.collections
437
+ }
438
+
439
+ export_data["export_date"] = datetime.now().isoformat()
440
+ export_data["version"] = "1.0"
441
+
442
+ with open(filepath, 'w', encoding='utf-8') as f:
443
+ json.dump(export_data, f, indent=2, ensure_ascii=False)
444
+
445
+ self.logger.info(f"History exported to {filepath}")
446
+ return True
447
+
448
+ except Exception as e:
449
+ self.logger.error(f"Error exporting history: {e}")
450
+ return False
451
+
452
+ def import_history(self, filepath: str, merge: bool = True) -> bool:
453
+ """
454
+ Import history from a file.
455
+
456
+ Args:
457
+ filepath: Import file path
458
+ merge: Whether to merge with existing history
459
+
460
+ Returns:
461
+ True if successful
462
+ """
463
+ try:
464
+ with open(filepath, 'r', encoding='utf-8') as f:
465
+ import_data = json.load(f)
466
+
467
+ if not merge:
468
+ self.history.clear()
469
+ self.collections.clear()
470
+
471
+ # Import items
472
+ items_data = import_data.get("items", [])
473
+ for item_data in items_data:
474
+ try:
475
+ item = RequestHistoryItem.from_dict(item_data)
476
+ # Check if item already exists (by timestamp)
477
+ if not any(h.timestamp == item.timestamp for h in self.history):
478
+ self.history.append(item)
479
+ except Exception as e:
480
+ self.logger.warning(f"Error importing history item: {e}")
481
+
482
+ # Import collections if present
483
+ if "collections" in import_data:
484
+ for name, timestamps in import_data["collections"].items():
485
+ if name not in self.collections:
486
+ self.collections[name] = []
487
+ # Add timestamps that don't already exist
488
+ for ts in timestamps:
489
+ if ts not in self.collections[name]:
490
+ self.collections[name].append(ts)
491
+
492
+ # Maintain max items limit
493
+ if len(self.history) > self.max_items:
494
+ # Sort by timestamp and keep newest
495
+ self.history.sort(key=lambda x: x.timestamp, reverse=True)
496
+ self.history = self.history[:self.max_items]
497
+
498
+ self.save_history()
499
+ self.logger.info(f"History imported from {filepath}")
500
+ return True
501
+
502
+ except Exception as e:
503
+ self.logger.error(f"Error importing history: {e}")
504
+ return False
505
+
506
+ def load_history(self) -> bool:
507
+ """
508
+ Load history from centralized settings.json file.
509
+
510
+ Returns:
511
+ True if successful
512
+ """
513
+ try:
514
+ if os.path.exists(self.history_file):
515
+ with open(self.history_file, 'r', encoding='utf-8') as f:
516
+ all_settings = json.load(f)
517
+
518
+ # Get tool settings from tool_settings section
519
+ tool_settings = all_settings.get("tool_settings", {})
520
+ curl_settings = tool_settings.get(self.tool_key, {})
521
+
522
+ # Load history items
523
+ self.history = []
524
+ for item_data in curl_settings.get("history", []):
525
+ try:
526
+ item = RequestHistoryItem.from_dict(item_data)
527
+ self.history.append(item)
528
+ except Exception as e:
529
+ self.logger.warning(f"Error loading history item: {e}")
530
+
531
+ # Load collections
532
+ self.collections = curl_settings.get("collections", {})
533
+
534
+ self.logger.info(f"Loaded {len(self.history)} history items from {self.history_file}")
535
+ return True
536
+ else:
537
+ self.logger.info("No history file found, starting with empty history")
538
+ return True
539
+
540
+ except Exception as e:
541
+ self.logger.error(f"Error loading history: {e}")
542
+ return False
543
+
544
+ def save_history(self) -> bool:
545
+ """
546
+ Save history to centralized settings.json file.
547
+
548
+ Returns:
549
+ True if successful
550
+ """
551
+ try:
552
+ # Load existing settings file
553
+ all_settings = {}
554
+ if os.path.exists(self.history_file):
555
+ with open(self.history_file, 'r', encoding='utf-8') as f:
556
+ all_settings = json.load(f)
557
+
558
+ # Ensure tool_settings section exists
559
+ if "tool_settings" not in all_settings:
560
+ all_settings["tool_settings"] = {}
561
+
562
+ # Ensure cURL Tool section exists
563
+ if self.tool_key not in all_settings["tool_settings"]:
564
+ all_settings["tool_settings"][self.tool_key] = {}
565
+
566
+ # Update history and collections in cURL Tool settings
567
+ all_settings["tool_settings"][self.tool_key]["history"] = [item.to_dict() for item in self.history]
568
+ all_settings["tool_settings"][self.tool_key]["collections"] = self.collections
569
+ all_settings["tool_settings"][self.tool_key]["history_last_updated"] = datetime.now().isoformat()
570
+ all_settings["tool_settings"][self.tool_key]["history_version"] = "1.0"
571
+
572
+ # Write settings to file
573
+ with open(self.history_file, 'w', encoding='utf-8') as f:
574
+ json.dump(all_settings, f, indent=4, ensure_ascii=False)
575
+
576
+ self.logger.debug(f"History saved to {self.history_file}")
577
+ return True
578
+
579
+ except Exception as e:
580
+ self.logger.error(f"Error saving history: {e}")
581
+ return False
582
+
583
+ def _remove_from_collections(self, timestamp: str):
584
+ """Remove a timestamp from all collections."""
585
+ for collection_name in list(self.collections.keys()):
586
+ if timestamp in self.collections[collection_name]:
587
+ self.collections[collection_name].remove(timestamp)
588
+
589
+ def _cleanup_collections(self):
590
+ """Clean up collections by removing references to non-existent items."""
591
+ for collection_name in list(self.collections.keys()):
592
+ # Filter out timestamps that don't exist in history
593
+ valid_timestamps = [
594
+ ts for ts in self.collections[collection_name]
595
+ if self.get_history_item(ts) is not None
596
+ ]
597
+ self.collections[collection_name] = valid_timestamps
598
+
599
+ # Remove empty collections
600
+ if not self.collections[collection_name]:
601
601
  del self.collections[collection_name]