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/core/backup_recovery_manager.py +169 -3
- package/core/data_directory.py +549 -0
- package/core/diff_utils.py +239 -0
- package/core/efficient_line_numbers.py +30 -0
- package/core/mcp/find_replace_diff.py +334 -0
- package/core/mcp/tool_registry.py +369 -9
- package/core/memento.py +275 -0
- package/mcp.json +1 -1
- package/migrate_data.py +127 -0
- package/package.json +5 -2
- package/pomera.py +408 -10
- package/pomera_mcp_server.py +2 -2
- package/requirements.txt +1 -0
- package/scripts/Dockerfile.alpine +43 -0
- package/scripts/Dockerfile.gui-test +54 -0
- package/scripts/Dockerfile.linux +43 -0
- package/scripts/Dockerfile.test-linux +80 -0
- package/scripts/Dockerfile.ubuntu +39 -0
- package/scripts/README.md +53 -0
- package/scripts/build-all.bat +113 -0
- package/scripts/build-docker.bat +53 -0
- package/scripts/build-docker.sh +55 -0
- package/scripts/build-optimized.bat +101 -0
- package/scripts/build.sh +78 -0
- package/scripts/docker-compose.test.yml +27 -0
- package/scripts/docker-compose.yml +32 -0
- package/scripts/postinstall.js +62 -0
- package/scripts/requirements-minimal.txt +33 -0
- package/scripts/test-linux-simple.bat +28 -0
- package/scripts/validate-release-workflow.py +450 -0
- package/tools/diff_viewer.py +797 -52
- package/tools/find_replace.py +551 -12
- package/tools/notes_widget.py +48 -8
- package/tools/regex_extractor.py +5 -5
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
|
|
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=
|
|
780
|
-
backup_path=
|
|
781
|
-
json_settings_path=
|
|
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
|
|
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."""
|
package/pomera_mcp_server.py
CHANGED
|
@@ -70,7 +70,7 @@ def main():
|
|
|
70
70
|
parser.add_argument(
|
|
71
71
|
"--version",
|
|
72
72
|
action="version",
|
|
73
|
-
version="pomera-mcp-server 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.
|
|
163
|
+
server_version="1.2.3"
|
|
164
164
|
)
|
|
165
165
|
|
|
166
166
|
logger.info("Starting Pomera MCP Server...")
|
package/requirements.txt
CHANGED
|
@@ -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/"]
|