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,422 +1,422 @@
1
- """
2
- Timestamp Converter Module - Date/time conversion utilities
3
-
4
- This module provides timestamp conversion functionality
5
- for the Pomera AI Commander application.
6
-
7
- Features:
8
- - Unix timestamp to human-readable date
9
- - Human-readable date to Unix timestamp
10
- - Multiple date format options
11
- - Relative time display
12
- """
13
-
14
- import tkinter as tk
15
- from tkinter import ttk
16
- from datetime import datetime, timezone
17
- import time
18
- import re
19
-
20
-
21
- class TimestampConverterProcessor:
22
- """Timestamp converter processor."""
23
-
24
- DATE_FORMATS = {
25
- "iso": "%Y-%m-%dT%H:%M:%S",
26
- "iso_date": "%Y-%m-%d",
27
- "us": "%m/%d/%Y %I:%M:%S %p",
28
- "us_date": "%m/%d/%Y",
29
- "eu": "%d/%m/%Y %H:%M:%S",
30
- "eu_date": "%d/%m/%Y",
31
- "long": "%B %d, %Y %H:%M:%S",
32
- "short": "%b %d, %Y %H:%M",
33
- "rfc2822": "%a, %d %b %Y %H:%M:%S",
34
- "custom": None
35
- }
36
-
37
- @staticmethod
38
- def unix_to_datetime(timestamp, use_utc=False):
39
- """Convert Unix timestamp to datetime object."""
40
- try:
41
- ts = float(timestamp)
42
- # Handle milliseconds
43
- if ts > 1e12:
44
- ts = ts / 1000
45
-
46
- if use_utc:
47
- return datetime.fromtimestamp(ts, tz=timezone.utc)
48
- else:
49
- return datetime.fromtimestamp(ts)
50
- except (ValueError, OSError) as e:
51
- return None
52
-
53
- @staticmethod
54
- def datetime_to_unix(dt):
55
- """Convert datetime object to Unix timestamp."""
56
- return int(dt.timestamp())
57
-
58
- @staticmethod
59
- def format_datetime(dt, format_name="iso", custom_format=None):
60
- """Format datetime using specified format."""
61
- if format_name == "custom" and custom_format:
62
- fmt = custom_format
63
- else:
64
- fmt = TimestampConverterProcessor.DATE_FORMATS.get(format_name, "%Y-%m-%d %H:%M:%S")
65
-
66
- try:
67
- return dt.strftime(fmt)
68
- except Exception:
69
- return str(dt)
70
-
71
- @staticmethod
72
- def parse_datetime(text, format_name="iso", custom_format=None):
73
- """Parse datetime from string."""
74
- text = text.strip()
75
-
76
- # Try to detect Unix timestamp
77
- if re.match(r'^\d{10,13}$', text):
78
- ts = float(text)
79
- if ts > 1e12:
80
- ts = ts / 1000
81
- return datetime.fromtimestamp(ts)
82
-
83
- # Try specified format
84
- if format_name == "custom" and custom_format:
85
- fmt = custom_format
86
- else:
87
- fmt = TimestampConverterProcessor.DATE_FORMATS.get(format_name)
88
-
89
- if fmt:
90
- try:
91
- return datetime.strptime(text, fmt)
92
- except ValueError:
93
- pass
94
-
95
- # Try common formats
96
- common_formats = [
97
- "%Y-%m-%dT%H:%M:%S",
98
- "%Y-%m-%d %H:%M:%S",
99
- "%Y-%m-%d",
100
- "%m/%d/%Y %H:%M:%S",
101
- "%m/%d/%Y",
102
- "%d/%m/%Y %H:%M:%S",
103
- "%d/%m/%Y",
104
- "%B %d, %Y",
105
- "%b %d, %Y",
106
- ]
107
-
108
- for fmt in common_formats:
109
- try:
110
- return datetime.strptime(text, fmt)
111
- except ValueError:
112
- continue
113
-
114
- return None
115
-
116
- @staticmethod
117
- def relative_time(dt):
118
- """Get relative time string (e.g., '2 hours ago')."""
119
- now = datetime.now()
120
- diff = now - dt
121
-
122
- seconds = diff.total_seconds()
123
-
124
- if seconds < 0:
125
- # Future
126
- seconds = abs(seconds)
127
- suffix = "from now"
128
- else:
129
- suffix = "ago"
130
-
131
- if seconds < 60:
132
- return f"{int(seconds)} seconds {suffix}"
133
- elif seconds < 3600:
134
- minutes = int(seconds / 60)
135
- return f"{minutes} minute{'s' if minutes != 1 else ''} {suffix}"
136
- elif seconds < 86400:
137
- hours = int(seconds / 3600)
138
- return f"{hours} hour{'s' if hours != 1 else ''} {suffix}"
139
- elif seconds < 2592000: # 30 days
140
- days = int(seconds / 86400)
141
- return f"{days} day{'s' if days != 1 else ''} {suffix}"
142
- elif seconds < 31536000: # 365 days
143
- months = int(seconds / 2592000)
144
- return f"{months} month{'s' if months != 1 else ''} {suffix}"
145
- else:
146
- years = int(seconds / 31536000)
147
- return f"{years} year{'s' if years != 1 else ''} {suffix}"
148
-
149
- @staticmethod
150
- def convert_timestamp(text, input_format="unix", output_format="iso",
151
- use_utc=False, custom_format=None, show_relative=False):
152
- """Convert timestamp between formats."""
153
- text = text.strip()
154
-
155
- if not text:
156
- return ""
157
-
158
- # Parse input
159
- if input_format == "unix":
160
- dt = TimestampConverterProcessor.unix_to_datetime(text, use_utc)
161
- else:
162
- dt = TimestampConverterProcessor.parse_datetime(text, input_format, custom_format)
163
-
164
- if dt is None:
165
- return f"Error: Could not parse '{text}'"
166
-
167
- # Format output
168
- if output_format == "unix":
169
- result = str(TimestampConverterProcessor.datetime_to_unix(dt))
170
- else:
171
- result = TimestampConverterProcessor.format_datetime(dt, output_format, custom_format)
172
-
173
- if show_relative:
174
- relative = TimestampConverterProcessor.relative_time(dt)
175
- result += f" ({relative})"
176
-
177
- return result
178
-
179
- @staticmethod
180
- def convert_batch(text, input_format="unix", output_format="iso",
181
- use_utc=False, custom_format=None, show_relative=False):
182
- """Convert multiple timestamps."""
183
- lines = text.strip().split('\n')
184
- results = []
185
-
186
- for line in lines:
187
- line = line.strip()
188
- if line:
189
- result = TimestampConverterProcessor.convert_timestamp(
190
- line, input_format, output_format, use_utc, custom_format, show_relative
191
- )
192
- results.append(result)
193
- else:
194
- results.append('')
195
-
196
- return '\n'.join(results)
197
-
198
- @staticmethod
199
- def get_current_timestamp():
200
- """Get current Unix timestamp."""
201
- return str(int(time.time()))
202
-
203
- @staticmethod
204
- def get_current_datetime(format_name="iso", custom_format=None):
205
- """Get current datetime in specified format."""
206
- return TimestampConverterProcessor.format_datetime(
207
- datetime.now(), format_name, custom_format
208
- )
209
-
210
-
211
- class TimestampConverterWidget(ttk.Frame):
212
- """Widget for timestamp converter tool."""
213
-
214
- def __init__(self, parent, app):
215
- super().__init__(parent)
216
- self.app = app
217
- self.processor = TimestampConverterProcessor()
218
-
219
- self.input_format = tk.StringVar(value="unix")
220
- self.output_format = tk.StringVar(value="iso")
221
- self.use_utc = tk.BooleanVar(value=False)
222
- self.custom_format = tk.StringVar(value="%Y-%m-%d %H:%M:%S")
223
- self.show_relative = tk.BooleanVar(value=False)
224
-
225
- self.create_widgets()
226
- self.load_settings()
227
-
228
- def create_widgets(self):
229
- """Creates the widget interface."""
230
- # Input format
231
- input_frame = ttk.LabelFrame(self, text="Input Format", padding=10)
232
- input_frame.pack(fill=tk.X, padx=5, pady=5)
233
-
234
- formats = [("Unix Timestamp", "unix"), ("ISO 8601", "iso"),
235
- ("US Date", "us"), ("EU Date", "eu"), ("Auto-detect", "auto")]
236
-
237
- for i, (text, value) in enumerate(formats):
238
- ttk.Radiobutton(input_frame, text=text,
239
- variable=self.input_format, value=value,
240
- command=self.on_setting_change).grid(row=i//3, column=i%3, sticky=tk.W, padx=5)
241
-
242
- # Output format
243
- output_frame = ttk.LabelFrame(self, text="Output Format", padding=10)
244
- output_frame.pack(fill=tk.X, padx=5, pady=5)
245
-
246
- output_formats = [
247
- ("Unix Timestamp", "unix"), ("ISO 8601", "iso"), ("ISO Date", "iso_date"),
248
- ("US Format", "us"), ("EU Format", "eu"), ("Long", "long"),
249
- ("Short", "short"), ("RFC 2822", "rfc2822"), ("Custom", "custom")
250
- ]
251
-
252
- for i, (text, value) in enumerate(output_formats):
253
- ttk.Radiobutton(output_frame, text=text,
254
- variable=self.output_format, value=value,
255
- command=self.on_setting_change).grid(row=i//3, column=i%3, sticky=tk.W, padx=5)
256
-
257
- # Custom format
258
- custom_frame = ttk.Frame(self)
259
- custom_frame.pack(fill=tk.X, padx=5, pady=5)
260
-
261
- ttk.Label(custom_frame, text="Custom Format:").pack(side=tk.LEFT)
262
- ttk.Entry(custom_frame, textvariable=self.custom_format, width=25).pack(side=tk.LEFT, padx=5)
263
- ttk.Label(custom_frame, text="(e.g., %Y-%m-%d)", font=('TkDefaultFont', 8)).pack(side=tk.LEFT)
264
-
265
- # Options
266
- options_frame = ttk.Frame(self)
267
- options_frame.pack(fill=tk.X, padx=5, pady=5)
268
-
269
- ttk.Checkbutton(options_frame, text="Use UTC",
270
- variable=self.use_utc,
271
- command=self.on_setting_change).pack(side=tk.LEFT, padx=10)
272
- ttk.Checkbutton(options_frame, text="Show Relative Time",
273
- variable=self.show_relative,
274
- command=self.on_setting_change).pack(side=tk.LEFT, padx=10)
275
-
276
- # Buttons
277
- buttons_frame = ttk.Frame(self)
278
- buttons_frame.pack(fill=tk.X, padx=5, pady=10)
279
-
280
- ttk.Button(buttons_frame, text="Convert",
281
- command=self.convert).pack(side=tk.LEFT, padx=5)
282
- ttk.Button(buttons_frame, text="Insert Current Time",
283
- command=self.insert_current).pack(side=tk.LEFT, padx=5)
284
-
285
- def load_settings(self):
286
- """Load settings from the application."""
287
- settings = self.app.settings.get("tool_settings", {}).get("Timestamp Converter", {})
288
-
289
- self.input_format.set(settings.get("input_format", "unix"))
290
- self.output_format.set(settings.get("output_format", "iso"))
291
- self.use_utc.set(settings.get("use_utc", False))
292
- self.custom_format.set(settings.get("custom_format", "%Y-%m-%d %H:%M:%S"))
293
- self.show_relative.set(settings.get("show_relative", False))
294
-
295
- def save_settings(self):
296
- """Save current settings to the application."""
297
- if "Timestamp Converter" not in self.app.settings["tool_settings"]:
298
- self.app.settings["tool_settings"]["Timestamp Converter"] = {}
299
-
300
- self.app.settings["tool_settings"]["Timestamp Converter"].update({
301
- "input_format": self.input_format.get(),
302
- "output_format": self.output_format.get(),
303
- "use_utc": self.use_utc.get(),
304
- "custom_format": self.custom_format.get(),
305
- "show_relative": self.show_relative.get()
306
- })
307
-
308
- self.app.save_settings()
309
-
310
- def on_setting_change(self, *args):
311
- """Handle setting changes."""
312
- self.save_settings()
313
-
314
- def convert(self):
315
- """Convert timestamps."""
316
- active_input_tab = self.app.input_tabs[self.app.input_notebook.index(self.app.input_notebook.select())]
317
- input_text = active_input_tab.text.get("1.0", tk.END).rstrip('\n')
318
-
319
- if not input_text.strip():
320
- return
321
-
322
- result = TimestampConverterProcessor.convert_batch(
323
- input_text,
324
- self.input_format.get(),
325
- self.output_format.get(),
326
- self.use_utc.get(),
327
- self.custom_format.get(),
328
- self.show_relative.get()
329
- )
330
-
331
- active_output_tab = self.app.output_tabs[self.app.output_notebook.index(self.app.output_notebook.select())]
332
- active_output_tab.text.config(state="normal")
333
- active_output_tab.text.delete("1.0", tk.END)
334
- active_output_tab.text.insert("1.0", result)
335
- active_output_tab.text.config(state="disabled")
336
-
337
- self.app.update_all_stats()
338
-
339
- def insert_current(self):
340
- """Insert current timestamp."""
341
- if self.output_format.get() == "unix":
342
- result = TimestampConverterProcessor.get_current_timestamp()
343
- else:
344
- result = TimestampConverterProcessor.get_current_datetime(
345
- self.output_format.get(), self.custom_format.get()
346
- )
347
-
348
- active_output_tab = self.app.output_tabs[self.app.output_notebook.index(self.app.output_notebook.select())]
349
- active_output_tab.text.config(state="normal")
350
- active_output_tab.text.delete("1.0", tk.END)
351
- active_output_tab.text.insert("1.0", result)
352
- active_output_tab.text.config(state="disabled")
353
-
354
- self.app.update_all_stats()
355
-
356
-
357
- class TimestampConverter:
358
- """Main class for Timestamp Converter integration."""
359
-
360
- def __init__(self):
361
- self.processor = TimestampConverterProcessor()
362
-
363
- def create_widget(self, parent, app):
364
- """Create and return the Timestamp Converter widget."""
365
- return TimestampConverterWidget(parent, app)
366
-
367
- def get_default_settings(self):
368
- """Return default settings for Timestamp Converter."""
369
- return {
370
- "input_format": "unix",
371
- "output_format": "iso",
372
- "use_utc": False,
373
- "custom_format": "%Y-%m-%d %H:%M:%S",
374
- "show_relative": False
375
- }
376
-
377
-
378
- # BaseTool-compatible wrapper
379
- try:
380
- from tools.base_tool import ToolWithOptions
381
- from typing import Dict, Any
382
-
383
- class TimestampConverterV2(ToolWithOptions):
384
- """
385
- BaseTool-compatible version of TimestampConverter.
386
- """
387
-
388
- TOOL_NAME = "Timestamp Converter"
389
- TOOL_DESCRIPTION = "Convert between Unix timestamps and human-readable dates"
390
- TOOL_VERSION = "2.0.0"
391
-
392
- OPTIONS = [
393
- ("Unix to ISO", "unix_to_iso"),
394
- ("Unix to US Format", "unix_to_us"),
395
- ("Unix to EU Format", "unix_to_eu"),
396
- ("Date to Unix", "date_to_unix"),
397
- ("Current Time", "now"),
398
- ]
399
- OPTIONS_LABEL = "Conversion"
400
- USE_DROPDOWN = True
401
- DEFAULT_OPTION = "unix_to_iso"
402
-
403
- def process_text(self, input_text: str, settings: Dict[str, Any]) -> str:
404
- """Process text using the specified conversion."""
405
- mode = settings.get("mode", "unix_to_iso")
406
-
407
- if mode == "now":
408
- import time
409
- return str(int(time.time()))
410
- elif mode == "unix_to_iso":
411
- return TimestampConverterProcessor.convert_batch(input_text, "unix", "iso")
412
- elif mode == "unix_to_us":
413
- return TimestampConverterProcessor.convert_batch(input_text, "unix", "us")
414
- elif mode == "unix_to_eu":
415
- return TimestampConverterProcessor.convert_batch(input_text, "unix", "eu")
416
- elif mode == "date_to_unix":
417
- return TimestampConverterProcessor.convert_batch(input_text, "auto", "unix")
418
- else:
419
- return input_text
420
-
421
- except ImportError:
1
+ """
2
+ Timestamp Converter Module - Date/time conversion utilities
3
+
4
+ This module provides timestamp conversion functionality
5
+ for the Pomera AI Commander application.
6
+
7
+ Features:
8
+ - Unix timestamp to human-readable date
9
+ - Human-readable date to Unix timestamp
10
+ - Multiple date format options
11
+ - Relative time display
12
+ """
13
+
14
+ import tkinter as tk
15
+ from tkinter import ttk
16
+ from datetime import datetime, timezone
17
+ import time
18
+ import re
19
+
20
+
21
+ class TimestampConverterProcessor:
22
+ """Timestamp converter processor."""
23
+
24
+ DATE_FORMATS = {
25
+ "iso": "%Y-%m-%dT%H:%M:%S",
26
+ "iso_date": "%Y-%m-%d",
27
+ "us": "%m/%d/%Y %I:%M:%S %p",
28
+ "us_date": "%m/%d/%Y",
29
+ "eu": "%d/%m/%Y %H:%M:%S",
30
+ "eu_date": "%d/%m/%Y",
31
+ "long": "%B %d, %Y %H:%M:%S",
32
+ "short": "%b %d, %Y %H:%M",
33
+ "rfc2822": "%a, %d %b %Y %H:%M:%S",
34
+ "custom": None
35
+ }
36
+
37
+ @staticmethod
38
+ def unix_to_datetime(timestamp, use_utc=False):
39
+ """Convert Unix timestamp to datetime object."""
40
+ try:
41
+ ts = float(timestamp)
42
+ # Handle milliseconds
43
+ if ts > 1e12:
44
+ ts = ts / 1000
45
+
46
+ if use_utc:
47
+ return datetime.fromtimestamp(ts, tz=timezone.utc)
48
+ else:
49
+ return datetime.fromtimestamp(ts)
50
+ except (ValueError, OSError) as e:
51
+ return None
52
+
53
+ @staticmethod
54
+ def datetime_to_unix(dt):
55
+ """Convert datetime object to Unix timestamp."""
56
+ return int(dt.timestamp())
57
+
58
+ @staticmethod
59
+ def format_datetime(dt, format_name="iso", custom_format=None):
60
+ """Format datetime using specified format."""
61
+ if format_name == "custom" and custom_format:
62
+ fmt = custom_format
63
+ else:
64
+ fmt = TimestampConverterProcessor.DATE_FORMATS.get(format_name, "%Y-%m-%d %H:%M:%S")
65
+
66
+ try:
67
+ return dt.strftime(fmt)
68
+ except Exception:
69
+ return str(dt)
70
+
71
+ @staticmethod
72
+ def parse_datetime(text, format_name="iso", custom_format=None):
73
+ """Parse datetime from string."""
74
+ text = text.strip()
75
+
76
+ # Try to detect Unix timestamp
77
+ if re.match(r'^\d{10,13}$', text):
78
+ ts = float(text)
79
+ if ts > 1e12:
80
+ ts = ts / 1000
81
+ return datetime.fromtimestamp(ts)
82
+
83
+ # Try specified format
84
+ if format_name == "custom" and custom_format:
85
+ fmt = custom_format
86
+ else:
87
+ fmt = TimestampConverterProcessor.DATE_FORMATS.get(format_name)
88
+
89
+ if fmt:
90
+ try:
91
+ return datetime.strptime(text, fmt)
92
+ except ValueError:
93
+ pass
94
+
95
+ # Try common formats
96
+ common_formats = [
97
+ "%Y-%m-%dT%H:%M:%S",
98
+ "%Y-%m-%d %H:%M:%S",
99
+ "%Y-%m-%d",
100
+ "%m/%d/%Y %H:%M:%S",
101
+ "%m/%d/%Y",
102
+ "%d/%m/%Y %H:%M:%S",
103
+ "%d/%m/%Y",
104
+ "%B %d, %Y",
105
+ "%b %d, %Y",
106
+ ]
107
+
108
+ for fmt in common_formats:
109
+ try:
110
+ return datetime.strptime(text, fmt)
111
+ except ValueError:
112
+ continue
113
+
114
+ return None
115
+
116
+ @staticmethod
117
+ def relative_time(dt):
118
+ """Get relative time string (e.g., '2 hours ago')."""
119
+ now = datetime.now()
120
+ diff = now - dt
121
+
122
+ seconds = diff.total_seconds()
123
+
124
+ if seconds < 0:
125
+ # Future
126
+ seconds = abs(seconds)
127
+ suffix = "from now"
128
+ else:
129
+ suffix = "ago"
130
+
131
+ if seconds < 60:
132
+ return f"{int(seconds)} seconds {suffix}"
133
+ elif seconds < 3600:
134
+ minutes = int(seconds / 60)
135
+ return f"{minutes} minute{'s' if minutes != 1 else ''} {suffix}"
136
+ elif seconds < 86400:
137
+ hours = int(seconds / 3600)
138
+ return f"{hours} hour{'s' if hours != 1 else ''} {suffix}"
139
+ elif seconds < 2592000: # 30 days
140
+ days = int(seconds / 86400)
141
+ return f"{days} day{'s' if days != 1 else ''} {suffix}"
142
+ elif seconds < 31536000: # 365 days
143
+ months = int(seconds / 2592000)
144
+ return f"{months} month{'s' if months != 1 else ''} {suffix}"
145
+ else:
146
+ years = int(seconds / 31536000)
147
+ return f"{years} year{'s' if years != 1 else ''} {suffix}"
148
+
149
+ @staticmethod
150
+ def convert_timestamp(text, input_format="unix", output_format="iso",
151
+ use_utc=False, custom_format=None, show_relative=False):
152
+ """Convert timestamp between formats."""
153
+ text = text.strip()
154
+
155
+ if not text:
156
+ return ""
157
+
158
+ # Parse input
159
+ if input_format == "unix":
160
+ dt = TimestampConverterProcessor.unix_to_datetime(text, use_utc)
161
+ else:
162
+ dt = TimestampConverterProcessor.parse_datetime(text, input_format, custom_format)
163
+
164
+ if dt is None:
165
+ return f"Error: Could not parse '{text}'"
166
+
167
+ # Format output
168
+ if output_format == "unix":
169
+ result = str(TimestampConverterProcessor.datetime_to_unix(dt))
170
+ else:
171
+ result = TimestampConverterProcessor.format_datetime(dt, output_format, custom_format)
172
+
173
+ if show_relative:
174
+ relative = TimestampConverterProcessor.relative_time(dt)
175
+ result += f" ({relative})"
176
+
177
+ return result
178
+
179
+ @staticmethod
180
+ def convert_batch(text, input_format="unix", output_format="iso",
181
+ use_utc=False, custom_format=None, show_relative=False):
182
+ """Convert multiple timestamps."""
183
+ lines = text.strip().split('\n')
184
+ results = []
185
+
186
+ for line in lines:
187
+ line = line.strip()
188
+ if line:
189
+ result = TimestampConverterProcessor.convert_timestamp(
190
+ line, input_format, output_format, use_utc, custom_format, show_relative
191
+ )
192
+ results.append(result)
193
+ else:
194
+ results.append('')
195
+
196
+ return '\n'.join(results)
197
+
198
+ @staticmethod
199
+ def get_current_timestamp():
200
+ """Get current Unix timestamp."""
201
+ return str(int(time.time()))
202
+
203
+ @staticmethod
204
+ def get_current_datetime(format_name="iso", custom_format=None):
205
+ """Get current datetime in specified format."""
206
+ return TimestampConverterProcessor.format_datetime(
207
+ datetime.now(), format_name, custom_format
208
+ )
209
+
210
+
211
+ class TimestampConverterWidget(ttk.Frame):
212
+ """Widget for timestamp converter tool."""
213
+
214
+ def __init__(self, parent, app):
215
+ super().__init__(parent)
216
+ self.app = app
217
+ self.processor = TimestampConverterProcessor()
218
+
219
+ self.input_format = tk.StringVar(value="unix")
220
+ self.output_format = tk.StringVar(value="iso")
221
+ self.use_utc = tk.BooleanVar(value=False)
222
+ self.custom_format = tk.StringVar(value="%Y-%m-%d %H:%M:%S")
223
+ self.show_relative = tk.BooleanVar(value=False)
224
+
225
+ self.create_widgets()
226
+ self.load_settings()
227
+
228
+ def create_widgets(self):
229
+ """Creates the widget interface."""
230
+ # Input format
231
+ input_frame = ttk.LabelFrame(self, text="Input Format", padding=10)
232
+ input_frame.pack(fill=tk.X, padx=5, pady=5)
233
+
234
+ formats = [("Unix Timestamp", "unix"), ("ISO 8601", "iso"),
235
+ ("US Date", "us"), ("EU Date", "eu"), ("Auto-detect", "auto")]
236
+
237
+ for i, (text, value) in enumerate(formats):
238
+ ttk.Radiobutton(input_frame, text=text,
239
+ variable=self.input_format, value=value,
240
+ command=self.on_setting_change).grid(row=i//3, column=i%3, sticky=tk.W, padx=5)
241
+
242
+ # Output format
243
+ output_frame = ttk.LabelFrame(self, text="Output Format", padding=10)
244
+ output_frame.pack(fill=tk.X, padx=5, pady=5)
245
+
246
+ output_formats = [
247
+ ("Unix Timestamp", "unix"), ("ISO 8601", "iso"), ("ISO Date", "iso_date"),
248
+ ("US Format", "us"), ("EU Format", "eu"), ("Long", "long"),
249
+ ("Short", "short"), ("RFC 2822", "rfc2822"), ("Custom", "custom")
250
+ ]
251
+
252
+ for i, (text, value) in enumerate(output_formats):
253
+ ttk.Radiobutton(output_frame, text=text,
254
+ variable=self.output_format, value=value,
255
+ command=self.on_setting_change).grid(row=i//3, column=i%3, sticky=tk.W, padx=5)
256
+
257
+ # Custom format
258
+ custom_frame = ttk.Frame(self)
259
+ custom_frame.pack(fill=tk.X, padx=5, pady=5)
260
+
261
+ ttk.Label(custom_frame, text="Custom Format:").pack(side=tk.LEFT)
262
+ ttk.Entry(custom_frame, textvariable=self.custom_format, width=25).pack(side=tk.LEFT, padx=5)
263
+ ttk.Label(custom_frame, text="(e.g., %Y-%m-%d)", font=('TkDefaultFont', 8)).pack(side=tk.LEFT)
264
+
265
+ # Options
266
+ options_frame = ttk.Frame(self)
267
+ options_frame.pack(fill=tk.X, padx=5, pady=5)
268
+
269
+ ttk.Checkbutton(options_frame, text="Use UTC",
270
+ variable=self.use_utc,
271
+ command=self.on_setting_change).pack(side=tk.LEFT, padx=10)
272
+ ttk.Checkbutton(options_frame, text="Show Relative Time",
273
+ variable=self.show_relative,
274
+ command=self.on_setting_change).pack(side=tk.LEFT, padx=10)
275
+
276
+ # Buttons
277
+ buttons_frame = ttk.Frame(self)
278
+ buttons_frame.pack(fill=tk.X, padx=5, pady=10)
279
+
280
+ ttk.Button(buttons_frame, text="Convert",
281
+ command=self.convert).pack(side=tk.LEFT, padx=5)
282
+ ttk.Button(buttons_frame, text="Insert Current Time",
283
+ command=self.insert_current).pack(side=tk.LEFT, padx=5)
284
+
285
+ def load_settings(self):
286
+ """Load settings from the application."""
287
+ settings = self.app.settings.get("tool_settings", {}).get("Timestamp Converter", {})
288
+
289
+ self.input_format.set(settings.get("input_format", "unix"))
290
+ self.output_format.set(settings.get("output_format", "iso"))
291
+ self.use_utc.set(settings.get("use_utc", False))
292
+ self.custom_format.set(settings.get("custom_format", "%Y-%m-%d %H:%M:%S"))
293
+ self.show_relative.set(settings.get("show_relative", False))
294
+
295
+ def save_settings(self):
296
+ """Save current settings to the application."""
297
+ if "Timestamp Converter" not in self.app.settings["tool_settings"]:
298
+ self.app.settings["tool_settings"]["Timestamp Converter"] = {}
299
+
300
+ self.app.settings["tool_settings"]["Timestamp Converter"].update({
301
+ "input_format": self.input_format.get(),
302
+ "output_format": self.output_format.get(),
303
+ "use_utc": self.use_utc.get(),
304
+ "custom_format": self.custom_format.get(),
305
+ "show_relative": self.show_relative.get()
306
+ })
307
+
308
+ self.app.save_settings()
309
+
310
+ def on_setting_change(self, *args):
311
+ """Handle setting changes."""
312
+ self.save_settings()
313
+
314
+ def convert(self):
315
+ """Convert timestamps."""
316
+ active_input_tab = self.app.input_tabs[self.app.input_notebook.index(self.app.input_notebook.select())]
317
+ input_text = active_input_tab.text.get("1.0", tk.END).rstrip('\n')
318
+
319
+ if not input_text.strip():
320
+ return
321
+
322
+ result = TimestampConverterProcessor.convert_batch(
323
+ input_text,
324
+ self.input_format.get(),
325
+ self.output_format.get(),
326
+ self.use_utc.get(),
327
+ self.custom_format.get(),
328
+ self.show_relative.get()
329
+ )
330
+
331
+ active_output_tab = self.app.output_tabs[self.app.output_notebook.index(self.app.output_notebook.select())]
332
+ active_output_tab.text.config(state="normal")
333
+ active_output_tab.text.delete("1.0", tk.END)
334
+ active_output_tab.text.insert("1.0", result)
335
+ active_output_tab.text.config(state="disabled")
336
+
337
+ self.app.update_all_stats()
338
+
339
+ def insert_current(self):
340
+ """Insert current timestamp."""
341
+ if self.output_format.get() == "unix":
342
+ result = TimestampConverterProcessor.get_current_timestamp()
343
+ else:
344
+ result = TimestampConverterProcessor.get_current_datetime(
345
+ self.output_format.get(), self.custom_format.get()
346
+ )
347
+
348
+ active_output_tab = self.app.output_tabs[self.app.output_notebook.index(self.app.output_notebook.select())]
349
+ active_output_tab.text.config(state="normal")
350
+ active_output_tab.text.delete("1.0", tk.END)
351
+ active_output_tab.text.insert("1.0", result)
352
+ active_output_tab.text.config(state="disabled")
353
+
354
+ self.app.update_all_stats()
355
+
356
+
357
+ class TimestampConverter:
358
+ """Main class for Timestamp Converter integration."""
359
+
360
+ def __init__(self):
361
+ self.processor = TimestampConverterProcessor()
362
+
363
+ def create_widget(self, parent, app):
364
+ """Create and return the Timestamp Converter widget."""
365
+ return TimestampConverterWidget(parent, app)
366
+
367
+ def get_default_settings(self):
368
+ """Return default settings for Timestamp Converter."""
369
+ return {
370
+ "input_format": "unix",
371
+ "output_format": "iso",
372
+ "use_utc": False,
373
+ "custom_format": "%Y-%m-%d %H:%M:%S",
374
+ "show_relative": False
375
+ }
376
+
377
+
378
+ # BaseTool-compatible wrapper
379
+ try:
380
+ from tools.base_tool import ToolWithOptions
381
+ from typing import Dict, Any
382
+
383
+ class TimestampConverterV2(ToolWithOptions):
384
+ """
385
+ BaseTool-compatible version of TimestampConverter.
386
+ """
387
+
388
+ TOOL_NAME = "Timestamp Converter"
389
+ TOOL_DESCRIPTION = "Convert between Unix timestamps and human-readable dates"
390
+ TOOL_VERSION = "2.0.0"
391
+
392
+ OPTIONS = [
393
+ ("Unix to ISO", "unix_to_iso"),
394
+ ("Unix to US Format", "unix_to_us"),
395
+ ("Unix to EU Format", "unix_to_eu"),
396
+ ("Date to Unix", "date_to_unix"),
397
+ ("Current Time", "now"),
398
+ ]
399
+ OPTIONS_LABEL = "Conversion"
400
+ USE_DROPDOWN = True
401
+ DEFAULT_OPTION = "unix_to_iso"
402
+
403
+ def process_text(self, input_text: str, settings: Dict[str, Any]) -> str:
404
+ """Process text using the specified conversion."""
405
+ mode = settings.get("mode", "unix_to_iso")
406
+
407
+ if mode == "now":
408
+ import time
409
+ return str(int(time.time()))
410
+ elif mode == "unix_to_iso":
411
+ return TimestampConverterProcessor.convert_batch(input_text, "unix", "iso")
412
+ elif mode == "unix_to_us":
413
+ return TimestampConverterProcessor.convert_batch(input_text, "unix", "us")
414
+ elif mode == "unix_to_eu":
415
+ return TimestampConverterProcessor.convert_batch(input_text, "unix", "eu")
416
+ elif mode == "date_to_unix":
417
+ return TimestampConverterProcessor.convert_batch(input_text, "auto", "unix")
418
+ else:
419
+ return input_text
420
+
421
+ except ImportError:
422
422
  pass