pomera-ai-commander 1.2.1 → 1.2.3

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.
package/pomera.py CHANGED
@@ -59,6 +59,18 @@ except ImportError:
59
59
  DATABASE_SETTINGS_AVAILABLE = False
60
60
  print("Database Settings Manager not available, falling back to JSON")
61
61
 
62
+ # Data Directory module import (cross-platform data storage)
63
+ try:
64
+ from core.data_directory import (
65
+ get_database_path, get_backup_dir, migrate_legacy_databases,
66
+ is_portable_mode, get_data_directory_info, check_portable_mode_warning,
67
+ check_and_execute_pending_migration, set_custom_data_directory, load_config
68
+ )
69
+ DATA_DIRECTORY_AVAILABLE = True
70
+ except ImportError:
71
+ DATA_DIRECTORY_AVAILABLE = False
72
+ print("Data Directory module not available, using legacy paths")
73
+
62
74
  # Case Tool module import
63
75
  try:
64
76
  from tools.case_tool import CaseTool
@@ -527,7 +539,24 @@ else:
527
539
  self.linenumbers.bind("<Button-5>", self._on_mousewheel)
528
540
  self.text.bind("<<Modified>>", self._on_text_modified)
529
541
  self.text.bind("<Configure>", self._on_text_modified)
542
+
543
+ # Paste events - insert undo separator after paste
544
+ self.text.bind("<<Paste>>", self._on_paste)
545
+ self.text.bind("<Control-v>", self._on_paste)
546
+ self.text.bind("<Control-V>", self._on_paste)
547
+ self.text.bind("<Shift-Insert>", self._on_paste)
548
+
530
549
  self._on_text_modified()
550
+
551
+ def _on_paste(self, event=None):
552
+ """Insert undo separator after paste to separate from subsequent typing."""
553
+ def insert_separator():
554
+ try:
555
+ self.text.edit_separator()
556
+ except Exception:
557
+ pass
558
+ self.after(10, insert_separator)
559
+
531
560
 
532
561
  def _on_text_scroll(self, *args):
533
562
  self.text.yview(*args)
@@ -582,9 +611,7 @@ class PromeraAISettingsManager:
582
611
  def get_pattern_library(self) -> List[Dict[str, str]]:
583
612
  """Get the regex pattern library."""
584
613
  # Initialize pattern library if it doesn't exist
585
- if ("pattern_library" not in self.app.settings or
586
- len(self.app.settings.get("pattern_library", [])) < 10):
587
-
614
+ if "pattern_library" not in self.app.settings:
588
615
  # Try to import and use the comprehensive pattern library
589
616
  try:
590
617
  from core.regex_pattern_library import RegexPatternLibrary
@@ -629,6 +656,11 @@ class PromeraAISettingsManager:
629
656
  self.app.logger.info(f"Added {added_count} common extraction patterns to pattern library")
630
657
 
631
658
  return pattern_library
659
+
660
+ def set_pattern_library(self, pattern_library: List[Dict[str, str]]):
661
+ """Explicitly update and persist the pattern library."""
662
+ self.app.settings["pattern_library"] = pattern_library
663
+ self.app.save_settings()
632
664
 
633
665
 
634
666
  class DialogSettingsAdapter:
@@ -748,7 +780,7 @@ class PromeraAIApp(tk.Tk):
748
780
  self.title("Pomera AI Commander")
749
781
  self.geometry(AppConfig.DEFAULT_WINDOW_SIZE)
750
782
 
751
- self._after_id = None
783
+ self._after_id = None
752
784
  self._regex_cache = {}
753
785
  self.ai_widgets = {}
754
786
  self.ai_provider_urls = {
@@ -775,10 +807,30 @@ class PromeraAIApp(tk.Tk):
775
807
  # Initialize database settings manager or fallback to JSON
776
808
  if DATABASE_SETTINGS_AVAILABLE:
777
809
  try:
810
+ # Check for and execute any pending data migration FIRST
811
+ # (before any database connections are opened)
812
+ if DATA_DIRECTORY_AVAILABLE:
813
+ migration_msg = check_and_execute_pending_migration()
814
+ if migration_msg:
815
+ print(f"Data migration completed: {migration_msg}")
816
+
817
+ # Migrate legacy databases if needed (console INFO log only)
818
+ migrate_legacy_databases()
819
+ db_path = get_database_path("settings.db")
820
+ backup_path = str(get_backup_dir() / "settings_backup.db")
821
+ json_path = get_database_path("settings.json")
822
+ if is_portable_mode():
823
+ print("Running in portable mode - data stored in installation directory")
824
+ else:
825
+ # Fallback to legacy relative paths
826
+ db_path = "settings.db"
827
+ backup_path = "settings_backup.db"
828
+ json_path = "settings.json"
829
+
778
830
  self.db_settings_manager = DatabaseSettingsManager(
779
- db_path="settings.db",
780
- backup_path="settings_backup.db",
781
- json_settings_path="settings.json"
831
+ db_path=db_path,
832
+ backup_path=backup_path,
833
+ json_settings_path=json_path
782
834
  )
783
835
  # Provide default settings to database manager
784
836
  self.db_settings_manager.set_default_settings_provider(self._get_default_settings)
@@ -897,9 +949,10 @@ class PromeraAIApp(tk.Tk):
897
949
  if CURL_TOOL_MODULE_AVAILABLE:
898
950
  self.bind_all("<Control-u>", lambda e: self.open_curl_tool_window())
899
951
 
900
- # Set up Notes widget keyboard shortcut (Ctrl+S for Save as Note)
952
+ # Set up Notes widget keyboard shortcuts
901
953
  if NOTES_WIDGET_MODULE_AVAILABLE:
902
- self.bind_all("<Control-s>", lambda e: self.save_as_note())
954
+ self.bind_all("<Control-s>", lambda e: self.save_as_note()) # Ctrl+S for Save as Note
955
+ self.bind_all("<Control-n>", lambda e: self.open_notes_widget()) # Ctrl+N for Notes window
903
956
 
904
957
  # Set up MCP Manager keyboard shortcut (Ctrl+M)
905
958
  if MCP_MANAGER_MODULE_AVAILABLE:
@@ -968,6 +1021,345 @@ class PromeraAIApp(tk.Tk):
968
1021
 
969
1022
  self.logger.info("Background maintenance tasks scheduled (including auto-backup)")
970
1023
 
1024
+ def _show_data_location_dialog(self):
1025
+ """Show the Data Location settings dialog."""
1026
+ from tkinter import filedialog
1027
+
1028
+ dialog = tk.Toplevel(self)
1029
+ dialog.title("Data Location Settings")
1030
+ dialog.withdraw() # Hide until centered
1031
+ dialog.transient(self)
1032
+
1033
+ # Get current data info
1034
+ if DATA_DIRECTORY_AVAILABLE:
1035
+ data_info = get_data_directory_info()
1036
+ current_path = data_info.get('user_data_dir', 'Unknown')
1037
+ portable = data_info.get('portable_mode', False)
1038
+ config = load_config()
1039
+ custom_path = config.get('data_directory')
1040
+ else:
1041
+ current_path = "Data directory module not available"
1042
+ portable = False
1043
+ custom_path = None
1044
+
1045
+ # Main frame with padding
1046
+ main_frame = ttk.Frame(dialog, padding="20")
1047
+ main_frame.pack(fill=tk.BOTH, expand=True)
1048
+
1049
+ # Current location
1050
+ ttk.Label(main_frame, text="Current Data Location:", font=('TkDefaultFont', 10, 'bold')).pack(anchor=tk.W)
1051
+
1052
+ path_frame = ttk.Frame(main_frame)
1053
+ path_frame.pack(fill=tk.X, pady=(5, 15))
1054
+
1055
+ path_entry = ttk.Entry(path_frame, width=60)
1056
+ path_entry.insert(0, current_path)
1057
+ path_entry.config(state='readonly')
1058
+ path_entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
1059
+
1060
+ # Mode selection
1061
+ ttk.Label(main_frame, text="Data Storage Mode:", font=('TkDefaultFont', 10, 'bold')).pack(anchor=tk.W, pady=(10, 5))
1062
+
1063
+ mode_var = tk.StringVar()
1064
+ if custom_path:
1065
+ mode_var.set("custom")
1066
+ elif portable:
1067
+ mode_var.set("portable")
1068
+ else:
1069
+ mode_var.set("platform")
1070
+
1071
+ custom_path_var = tk.StringVar(value=custom_path or "")
1072
+
1073
+ # Platform default option
1074
+ platform_frame = ttk.Frame(main_frame)
1075
+ platform_frame.pack(fill=tk.X, pady=2)
1076
+ ttk.Radiobutton(platform_frame, text="Use platform default (recommended)",
1077
+ variable=mode_var, value="platform").pack(anchor=tk.W)
1078
+
1079
+ # Custom location option
1080
+ custom_frame = ttk.Frame(main_frame)
1081
+ custom_frame.pack(fill=tk.X, pady=2)
1082
+ ttk.Radiobutton(custom_frame, text="Use custom location:",
1083
+ variable=mode_var, value="custom").pack(side=tk.LEFT)
1084
+
1085
+ custom_entry = ttk.Entry(custom_frame, textvariable=custom_path_var, width=40)
1086
+ custom_entry.pack(side=tk.LEFT, padx=5)
1087
+
1088
+ def browse_folder():
1089
+ folder = filedialog.askdirectory(title="Select Data Directory")
1090
+ if folder:
1091
+ custom_path_var.set(folder)
1092
+ mode_var.set("custom")
1093
+
1094
+ ttk.Button(custom_frame, text="Browse...", command=browse_folder).pack(side=tk.LEFT)
1095
+
1096
+ # Portable mode option
1097
+ portable_frame = ttk.Frame(main_frame)
1098
+ portable_frame.pack(fill=tk.X, pady=2)
1099
+ ttk.Radiobutton(portable_frame, text="Portable mode (store in app folder - NOT RECOMMENDED)",
1100
+ variable=mode_var, value="portable").pack(anchor=tk.W)
1101
+
1102
+ # Warning for portable mode
1103
+ warning_frame = ttk.Frame(main_frame)
1104
+ warning_frame.pack(fill=tk.X, pady=(15, 10))
1105
+
1106
+ warning_label = ttk.Label(warning_frame,
1107
+ text="⚠️ Warning: Portable mode data will be LOST during npm/pip updates!",
1108
+ foreground='red')
1109
+
1110
+ def update_warning(*args):
1111
+ if mode_var.get() == "portable":
1112
+ warning_label.pack(anchor=tk.W)
1113
+ else:
1114
+ warning_label.pack_forget()
1115
+
1116
+ mode_var.trace_add('write', update_warning)
1117
+ update_warning()
1118
+
1119
+ # Migrate checkbox
1120
+ migrate_var = tk.BooleanVar(value=True)
1121
+ ttk.Checkbutton(main_frame, text="Migrate existing data to new location",
1122
+ variable=migrate_var).pack(anchor=tk.W, pady=(10, 0))
1123
+
1124
+ # Buttons
1125
+ button_frame = ttk.Frame(main_frame)
1126
+ button_frame.pack(fill=tk.X, pady=(20, 0))
1127
+
1128
+ def apply_changes():
1129
+ mode = mode_var.get()
1130
+ migrate = migrate_var.get()
1131
+
1132
+ if mode == "platform":
1133
+ new_path = None
1134
+ elif mode == "custom":
1135
+ new_path = custom_path_var.get()
1136
+ if not new_path:
1137
+ messagebox.showerror("Error", "Please specify a custom directory path.")
1138
+ return
1139
+ else: # portable
1140
+ # For portable mode, set to installation directory
1141
+ if DATA_DIRECTORY_AVAILABLE:
1142
+ from core.data_directory import _get_installation_dir
1143
+ new_path = str(_get_installation_dir())
1144
+ else:
1145
+ messagebox.showerror("Error", "Portable mode not available.")
1146
+ return
1147
+
1148
+ if DATA_DIRECTORY_AVAILABLE:
1149
+ result = set_custom_data_directory(new_path, migrate=migrate)
1150
+
1151
+ if result.get("restart_required"):
1152
+ dialog.destroy()
1153
+ response = messagebox.askyesno(
1154
+ "Restart Required",
1155
+ "Data location changed. The application needs to restart to migrate data.\n\n"
1156
+ "Close the application now?",
1157
+ icon='warning'
1158
+ )
1159
+ if response:
1160
+ self.on_closing()
1161
+ else:
1162
+ dialog.destroy()
1163
+ messagebox.showinfo("Success", result.get("message", "Settings saved."))
1164
+ else:
1165
+ messagebox.showerror("Error", "Data directory module not available.")
1166
+
1167
+ ttk.Button(button_frame, text="Cancel", command=dialog.destroy).pack(side=tk.RIGHT, padx=5)
1168
+ ttk.Button(button_frame, text="Apply & Restart", command=apply_changes).pack(side=tk.RIGHT)
1169
+
1170
+ # Center and show dialog (after all widgets created to avoid blink)
1171
+ dialog.update_idletasks()
1172
+ dialog.geometry("550x380")
1173
+ x = self.winfo_x() + (self.winfo_width() - 550) // 2
1174
+ y = self.winfo_y() + (self.winfo_height() - 380) // 2
1175
+ dialog.geometry(f"550x380+{x}+{y}")
1176
+ dialog.deiconify()
1177
+ dialog.grab_set()
1178
+
1179
+ def _show_update_dialog(self):
1180
+ """Show the Check for Updates dialog with GitHub version check."""
1181
+ import webbrowser
1182
+ import urllib.request
1183
+ import json
1184
+ import platform
1185
+
1186
+ dialog = tk.Toplevel(self)
1187
+ dialog.title("Check for Updates")
1188
+ dialog.withdraw() # Hide until centered
1189
+ dialog.transient(self)
1190
+
1191
+ main_frame = ttk.Frame(dialog, padding="20")
1192
+ main_frame.pack(fill=tk.BOTH, expand=True)
1193
+
1194
+ ttk.Label(main_frame, text="Pomera AI Commander", font=('TkDefaultFont', 14, 'bold')).pack(pady=(0, 10))
1195
+
1196
+ # Get current version - try pyproject.toml first (for local dev), then importlib.metadata
1197
+ current_version = None
1198
+ try:
1199
+ # Try reading from pyproject.toml (when running from source)
1200
+ import re
1201
+ pyproject_path = Path(__file__).parent / "pyproject.toml"
1202
+ if pyproject_path.exists():
1203
+ content = pyproject_path.read_text()
1204
+ match = re.search(r'version = "1.2.3"]+)"', content)
1205
+ if match:
1206
+ current_version = match.group(1)
1207
+ except Exception:
1208
+ pass
1209
+
1210
+ if not current_version:
1211
+ try:
1212
+ import importlib.metadata
1213
+ current_version = importlib.metadata.version("pomera-ai-commander")
1214
+ except Exception:
1215
+ current_version = "1.2.3"
1216
+
1217
+ # Detect OS for download link
1218
+ system = platform.system()
1219
+ if system == "Windows":
1220
+ os_pattern = "windows"
1221
+ os_name = "Windows"
1222
+ elif system == "Darwin":
1223
+ os_pattern = "macos"
1224
+ os_name = "macOS"
1225
+ else:
1226
+ os_pattern = "linux"
1227
+ os_name = "Linux"
1228
+
1229
+ # Fetch latest version from GitHub
1230
+ latest_version = None
1231
+ update_available = False
1232
+ download_url = None
1233
+ release_data = None
1234
+
1235
+ try:
1236
+ url = "https://api.github.com/repos/matbanik/Pomera-AI-Commander/releases/latest"
1237
+ req = urllib.request.Request(url, headers={'User-Agent': 'Pomera-AI-Commander'})
1238
+ with urllib.request.urlopen(req, timeout=5) as response:
1239
+ release_data = json.loads(response.read().decode())
1240
+ latest_version = release_data.get('tag_name', '').lstrip('v')
1241
+
1242
+ # Compare versions
1243
+ if current_version != "Unknown" and latest_version:
1244
+ try:
1245
+ current_parts = [int(x) for x in current_version.split('.')]
1246
+ latest_parts = [int(x) for x in latest_version.split('.')]
1247
+ update_available = latest_parts > current_parts
1248
+ except ValueError:
1249
+ update_available = current_version != latest_version
1250
+
1251
+ # Find platform-specific download URL
1252
+ assets = release_data.get('assets', [])
1253
+ for asset in assets:
1254
+ name = asset.get('name', '').lower()
1255
+ if os_pattern in name and (name.endswith('.exe') or name.endswith('.zip') or name.endswith('.tar.gz')):
1256
+ download_url = asset.get('browser_download_url')
1257
+ break
1258
+ except Exception as e:
1259
+ latest_version = f"Unable to check ({type(e).__name__})"
1260
+
1261
+ # Version info frame
1262
+ version_frame = ttk.Frame(main_frame)
1263
+ version_frame.pack(fill=tk.X, pady=(0, 15))
1264
+
1265
+ ttk.Label(version_frame, text=f"Current Version: {current_version}").pack(anchor=tk.W)
1266
+
1267
+ if latest_version and not latest_version.startswith("Unable"):
1268
+ if update_available:
1269
+ # Update available - BOLD BLACK
1270
+ ttk.Label(version_frame, text=f"Latest Version: {latest_version}",
1271
+ font=('TkDefaultFont', 11, 'bold')).pack(anchor=tk.W)
1272
+ ttk.Label(version_frame, text="⬆️ Update available!",
1273
+ font=('TkDefaultFont', 11, 'bold')).pack(anchor=tk.W, pady=(5, 0))
1274
+ else:
1275
+ # Up to date - GREEN
1276
+ ttk.Label(version_frame, text=f"Latest Version: {latest_version}",
1277
+ foreground='green').pack(anchor=tk.W)
1278
+ ttk.Label(version_frame, text="✓ You're up to date!",
1279
+ foreground='green', font=('TkDefaultFont', 10, 'bold')).pack(anchor=tk.W, pady=(5, 0))
1280
+ elif latest_version:
1281
+ ttk.Label(version_frame, text=f"Latest Version: {latest_version}", foreground='orange').pack(anchor=tk.W)
1282
+
1283
+ # Data location info
1284
+ if DATA_DIRECTORY_AVAILABLE:
1285
+ data_info = get_data_directory_info()
1286
+ mode = "Portable" if data_info.get('portable_mode') else "Platform"
1287
+ ttk.Label(main_frame, text=f"Installation Mode: {mode}").pack(anchor=tk.W)
1288
+ ttk.Label(main_frame, text=f"Data Location: {data_info.get('user_data_dir', 'Unknown')}",
1289
+ wraplength=500).pack(anchor=tk.W, pady=(0, 10))
1290
+
1291
+ # Warning for portable mode
1292
+ if data_info.get('portable_mode'):
1293
+ warning_frame = ttk.Frame(main_frame)
1294
+ warning_frame.pack(fill=tk.X, pady=5)
1295
+ ttk.Label(warning_frame,
1296
+ text="⚠️ PORTABLE MODE: Backup your data before updating!",
1297
+ foreground='red', font=('TkDefaultFont', 10, 'bold')).pack()
1298
+
1299
+ # Download link if update available
1300
+ if update_available and download_url:
1301
+ download_frame = ttk.Frame(main_frame)
1302
+ download_frame.pack(fill=tk.X, pady=(10, 0))
1303
+ ttk.Label(download_frame, text=f"📥 {os_name} Download:", font=('TkDefaultFont', 10, 'bold')).pack(anchor=tk.W)
1304
+
1305
+ # Create a clickable link-style label
1306
+ link_label = ttk.Label(download_frame, text=download_url.split('/')[-1],
1307
+ foreground='blue', cursor='hand2', font=('TkDefaultFont', 9, 'underline'))
1308
+ link_label.pack(anchor=tk.W)
1309
+ link_label.bind('<Button-1>', lambda e: webbrowser.open(download_url))
1310
+
1311
+ # Buttons
1312
+ button_frame = ttk.Frame(main_frame)
1313
+ button_frame.pack(fill=tk.X, pady=(15, 0))
1314
+
1315
+ def open_releases():
1316
+ webbrowser.open("https://github.com/matbanik/Pomera-AI-Commander/releases")
1317
+
1318
+ def download_now():
1319
+ if download_url:
1320
+ webbrowser.open(download_url)
1321
+
1322
+ def create_backup():
1323
+ if DATA_DIRECTORY_AVAILABLE:
1324
+ from core.data_directory import check_portable_mode_warning
1325
+ warning_info = check_portable_mode_warning(show_console_warning=False)
1326
+ if warning_info:
1327
+ # Create backup
1328
+ from core.mcp.tool_registry import ToolRegistry
1329
+ registry = ToolRegistry(register_builtins=False)
1330
+ registry._register_safe_update_tool()
1331
+ result = registry.execute('pomera_safe_update', {'action': 'backup'})
1332
+ messagebox.showinfo("Backup", f"Backup created!\n\n{result.content[0]['text'] if result.content else 'Check Documents/pomera-backup folder'}")
1333
+ else:
1334
+ messagebox.showinfo("Backup", "No backup needed - your data is safely stored in platform directories.")
1335
+
1336
+ if update_available and download_url:
1337
+ ttk.Button(button_frame, text=f"Download for {os_name}", command=download_now).pack(side=tk.LEFT, padx=5)
1338
+ ttk.Button(button_frame, text="Create Backup", command=create_backup).pack(side=tk.LEFT, padx=5)
1339
+ ttk.Button(button_frame, text="All Releases", command=open_releases).pack(side=tk.LEFT, padx=5)
1340
+ ttk.Button(button_frame, text="Close", command=dialog.destroy).pack(side=tk.RIGHT, padx=5)
1341
+
1342
+ # Center and show dialog (after all widgets created to avoid blink)
1343
+ dialog.update_idletasks()
1344
+ dialog.geometry("550x400")
1345
+ x = self.winfo_x() + (self.winfo_width() - 550) // 2
1346
+ y = self.winfo_y() + (self.winfo_height() - 400) // 2
1347
+ dialog.geometry(f"550x400+{x}+{y}")
1348
+ dialog.deiconify()
1349
+ dialog.grab_set()
1350
+
1351
+ def _show_about_dialog(self):
1352
+ """Show About dialog."""
1353
+ # Version managed by bump_version.py script
1354
+ version = "1.2.3"
1355
+
1356
+ messagebox.showinfo(
1357
+ "About Pomera AI Commander",
1358
+ f"Pomera AI Commander v{version}\n\n"
1359
+ "Text processing toolkit with MCP tools for AI assistants.\n\n"
1360
+ "https://github.com/matbanik/Pomera-AI-Commander"
1361
+ )
1362
+
971
1363
  def _auto_save_settings(self):
972
1364
  """Auto-save settings periodically (called by Task Scheduler)."""
973
1365
  try:
@@ -2604,6 +2996,7 @@ class PromeraAIApp(tk.Tk):
2604
2996
  # File Menu
2605
2997
  file_menu = tk.Menu(menubar, tearoff=0)
2606
2998
  menubar.add_cascade(label="File", menu=file_menu)
2999
+ file_menu.add_command(label="Data Location...", command=self._show_data_location_dialog)
2607
3000
  file_menu.add_command(label="Export Location", command=self.browse_export_path)
2608
3001
  file_menu.add_separator()
2609
3002
 
@@ -2694,7 +3087,8 @@ class PromeraAIApp(tk.Tk):
2694
3087
  if NOTES_WIDGET_MODULE_AVAILABLE:
2695
3088
  widgets_menu.add_command(
2696
3089
  label="Notes",
2697
- command=self.open_notes_widget
3090
+ command=self.open_notes_widget,
3091
+ accelerator="Ctrl+N"
2698
3092
  )
2699
3093
 
2700
3094
  # Add MCP Manager if available
@@ -2708,9 +3102,13 @@ class PromeraAIApp(tk.Tk):
2708
3102
  # Help Menu
2709
3103
  help_menu = tk.Menu(menubar, tearoff=0)
2710
3104
  menubar.add_cascade(label="Help", menu=help_menu)
3105
+ help_menu.add_command(label="Check for Updates...", command=self._show_update_dialog)
3106
+ help_menu.add_separator()
2711
3107
  help_menu.add_command(label="GitHub", command=lambda: webbrowser.open_new("https://github.com/matbanik/Pomera-AI-Commander"))
2712
3108
  help_menu.add_command(label="Report Issue", command=self.open_report_issue)
2713
3109
  help_menu.add_command(label="Ask AI", command=self.open_ai_tools)
3110
+ help_menu.add_separator()
3111
+ help_menu.add_command(label="About", command=self._show_about_dialog)
2714
3112
 
2715
3113
  def open_report_issue(self):
2716
3114
  """Opens the GitHub issues page in the default browser."""
@@ -70,7 +70,7 @@ def main():
70
70
  parser.add_argument(
71
71
  "--version",
72
72
  action="version",
73
- version="pomera-mcp-server 1.1.1"
73
+ version="pomera-mcp-server 1.2.3"
74
74
  )
75
75
  parser.add_argument(
76
76
  "--list-tools",
@@ -160,7 +160,7 @@ def main():
160
160
  server = StdioMCPServer(
161
161
  tool_registry=registry,
162
162
  server_name="pomera-mcp-server",
163
- server_version="1.1.1"
163
+ server_version="1.2.3"
164
164
  )
165
165
 
166
166
  logger.info("Starting Pomera MCP Server...")
package/requirements.txt CHANGED
@@ -3,6 +3,7 @@ requests>=2.25.0
3
3
  reportlab>=3.6.0
4
4
  python-docx>=0.8.11
5
5
  aiohttp>=3.9.0
6
+ platformdirs>=4.0.0
6
7
 
7
8
  # AI Tools dependencies
8
9
  huggingface-hub>=0.16.0
@@ -0,0 +1,43 @@
1
+ # Alpine Linux build - creates smaller, more portable executables
2
+ FROM python:3.11-alpine
3
+
4
+ # Install build dependencies
5
+ RUN apk add --no-cache \
6
+ gcc \
7
+ musl-dev \
8
+ libffi-dev \
9
+ g++ \
10
+ make
11
+
12
+ # Set working directory
13
+ WORKDIR /app
14
+
15
+ # Copy requirements and install dependencies
16
+ COPY requirements.txt .
17
+ RUN pip install --no-cache-dir -r requirements.txt
18
+
19
+ # Install additional AI SDK dependencies (may fail gracefully)
20
+ RUN pip install --no-cache-dir google-genai>=1.0.0 || echo "google-genai installation skipped"
21
+ RUN pip install --no-cache-dir azure-ai-inference>=1.0.0b1 azure-core>=1.30.0 || echo "azure-ai-inference installation skipped"
22
+ RUN pip install --no-cache-dir tenacity>=8.2.0 || echo "tenacity installation skipped"
23
+ RUN pip install --no-cache-dir aiohttp>=3.9.0 || echo "aiohttp installation skipped"
24
+
25
+ # Copy source code
26
+ COPY . .
27
+
28
+ # Build static executable with enhanced options
29
+ RUN pyinstaller \
30
+ --onefile \
31
+ --name pomera-linux-alpine \
32
+ --strip \
33
+ --noupx \
34
+ --clean \
35
+ --noconfirm \
36
+ pomera.py
37
+
38
+ # Copy to output
39
+ RUN mkdir -p /output && \
40
+ cp dist/pomera-linux-alpine /output/ && \
41
+ chmod +x /output/pomera-linux-alpine
42
+
43
+ CMD ["cp", "/output/pomera-linux-alpine", "/output/"]
@@ -0,0 +1,54 @@
1
+ # Lightweight Linux GUI for testing executables
2
+ FROM ubuntu:22.04
3
+
4
+ ENV DEBIAN_FRONTEND=noninteractive
5
+
6
+ # Install minimal GUI and VNC
7
+ RUN apt-get update && apt-get install -y \
8
+ xfce4-session \
9
+ xfce4-panel \
10
+ xfce4-terminal \
11
+ xfwm4 \
12
+ thunar \
13
+ tightvncserver \
14
+ novnc \
15
+ websockify \
16
+ python3 \
17
+ python3-tk \
18
+ file \
19
+ && apt-get clean
20
+
21
+ # Create test user
22
+ RUN useradd -m -s /bin/bash tester
23
+ RUN echo 'tester:test123' | chpasswd
24
+
25
+ # Setup VNC for tester
26
+ USER tester
27
+ WORKDIR /home/tester
28
+
29
+ RUN mkdir -p ~/.vnc
30
+ RUN echo 'test123' | vncpasswd -f > ~/.vnc/passwd
31
+ RUN chmod 600 ~/.vnc/passwd
32
+
33
+ # VNC startup script
34
+ RUN echo '#!/bin/bash' > ~/.vnc/xstartup && \
35
+ echo 'export XKL_XMODMAP_DISABLE=1' >> ~/.vnc/xstartup && \
36
+ echo 'unset SESSION_MANAGER' >> ~/.vnc/xstartup && \
37
+ echo 'unset DBUS_SESSION_BUS_ADDRESS' >> ~/.vnc/xstartup && \
38
+ echo 'startxfce4 &' >> ~/.vnc/xstartup && \
39
+ chmod +x ~/.vnc/xstartup
40
+
41
+ # Create app directory
42
+ RUN mkdir -p ~/app
43
+
44
+ USER root
45
+
46
+ # Start script
47
+ RUN echo '#!/bin/bash' > /start.sh && \
48
+ echo 'su - tester -c "vncserver :1 -geometry 1280x720 -depth 24"' >> /start.sh && \
49
+ echo 'websockify --web=/usr/share/novnc/ 6080 localhost:5901' >> /start.sh && \
50
+ chmod +x /start.sh
51
+
52
+ EXPOSE 6080
53
+
54
+ CMD ["/start.sh"]
@@ -0,0 +1,43 @@
1
+ # Dockerfile for building Linux executables
2
+ FROM python:3.11-slim
3
+
4
+ # Install system dependencies
5
+ RUN apt-get update && apt-get install -y \
6
+ gcc \
7
+ g++ \
8
+ libc6-dev \
9
+ libffi-dev \
10
+ && rm -rf /var/lib/apt/lists/*
11
+
12
+ # Set working directory
13
+ WORKDIR /app
14
+
15
+ # Copy requirements first for better caching
16
+ COPY requirements.txt .
17
+
18
+ # Install Python dependencies
19
+ RUN pip install --no-cache-dir -r requirements.txt
20
+
21
+ # Install additional AI SDK dependencies (may fail gracefully)
22
+ RUN pip install --no-cache-dir google-genai>=1.0.0 || echo "google-genai installation skipped"
23
+ RUN pip install --no-cache-dir azure-ai-inference>=1.0.0b1 azure-core>=1.30.0 || echo "azure-ai-inference installation skipped"
24
+ RUN pip install --no-cache-dir tenacity>=8.2.0 || echo "tenacity installation skipped"
25
+ RUN pip install --no-cache-dir aiohttp>=3.9.0 || echo "aiohttp installation skipped"
26
+
27
+ # Copy source code
28
+ COPY . .
29
+
30
+ # Build the executable
31
+ RUN pyinstaller --onefile --name pomera-linux pomera.py
32
+
33
+ # Create output directory
34
+ RUN mkdir -p /output
35
+
36
+ # Copy executable to output
37
+ RUN cp dist/pomera-linux /output/
38
+
39
+ # Set executable permissions
40
+ RUN chmod +x /output/pomera-linux
41
+
42
+ # Default command
43
+ CMD ["cp", "/output/pomera-linux", "/host-output/"]