pomera-ai-commander 1.2.2 → 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/mcp/tool_registry.py +40 -7
- package/mcp.json +1 -1
- package/package.json +1 -1
- package/pomera.py +4 -7
- package/pomera_mcp_server.py +2 -2
- package/tools/notes_widget.py +40 -5
|
@@ -1868,13 +1868,41 @@ class ToolRegistry:
|
|
|
1868
1868
|
conn.row_factory = sqlite3.Row
|
|
1869
1869
|
return conn
|
|
1870
1870
|
|
|
1871
|
+
def _sanitize_text(self, text: str) -> str:
|
|
1872
|
+
"""
|
|
1873
|
+
Sanitize text by removing invalid UTF-8 surrogate characters.
|
|
1874
|
+
|
|
1875
|
+
Lone surrogates (U+D800 to U+DFFF) are invalid in UTF-8 and cause
|
|
1876
|
+
encoding errors. This function removes them while preserving valid content.
|
|
1877
|
+
|
|
1878
|
+
Args:
|
|
1879
|
+
text: Input text that may contain invalid surrogates
|
|
1880
|
+
|
|
1881
|
+
Returns:
|
|
1882
|
+
Sanitized text safe for UTF-8 encoding and database storage
|
|
1883
|
+
"""
|
|
1884
|
+
if not text:
|
|
1885
|
+
return text
|
|
1886
|
+
|
|
1887
|
+
# Encode to UTF-8 with 'surrogatepass' to handle surrogates,
|
|
1888
|
+
# then decode with 'replace' to replace invalid sequences
|
|
1889
|
+
try:
|
|
1890
|
+
# This two-step process handles lone surrogates:
|
|
1891
|
+
# 1. surrogatepass allows encoding surrogates (normally forbidden in UTF-8)
|
|
1892
|
+
# 2. errors='replace' replaces invalid sequences with replacement char
|
|
1893
|
+
sanitized = text.encode('utf-8', errors='surrogatepass').decode('utf-8', errors='replace')
|
|
1894
|
+
return sanitized
|
|
1895
|
+
except Exception:
|
|
1896
|
+
# Fallback: manually filter out surrogate characters
|
|
1897
|
+
return ''.join(c for c in text if not (0xD800 <= ord(c) <= 0xDFFF))
|
|
1898
|
+
|
|
1871
1899
|
def _handle_notes_save(self, args: Dict[str, Any]) -> str:
|
|
1872
1900
|
"""Handle saving a new note."""
|
|
1873
1901
|
from datetime import datetime
|
|
1874
1902
|
|
|
1875
|
-
title = args.get("title", "")
|
|
1876
|
-
input_content = args.get("input_content", "")
|
|
1877
|
-
output_content = args.get("output_content", "")
|
|
1903
|
+
title = self._sanitize_text(args.get("title", ""))
|
|
1904
|
+
input_content = self._sanitize_text(args.get("input_content", ""))
|
|
1905
|
+
output_content = self._sanitize_text(args.get("output_content", ""))
|
|
1878
1906
|
|
|
1879
1907
|
if not title:
|
|
1880
1908
|
return "Error: Title is required"
|
|
@@ -2042,15 +2070,15 @@ class ToolRegistry:
|
|
|
2042
2070
|
|
|
2043
2071
|
if "title" in args:
|
|
2044
2072
|
updates.append("Title = ?")
|
|
2045
|
-
values.append(args["title"])
|
|
2073
|
+
values.append(self._sanitize_text(args["title"]))
|
|
2046
2074
|
|
|
2047
2075
|
if "input_content" in args:
|
|
2048
2076
|
updates.append("Input = ?")
|
|
2049
|
-
values.append(args["input_content"])
|
|
2077
|
+
values.append(self._sanitize_text(args["input_content"]))
|
|
2050
2078
|
|
|
2051
2079
|
if "output_content" in args:
|
|
2052
2080
|
updates.append("Output = ?")
|
|
2053
|
-
values.append(args["output_content"])
|
|
2081
|
+
values.append(self._sanitize_text(args["output_content"]))
|
|
2054
2082
|
|
|
2055
2083
|
if not updates:
|
|
2056
2084
|
conn.close()
|
|
@@ -2639,12 +2667,17 @@ class ToolRegistry:
|
|
|
2639
2667
|
"""Save operation to notes and return note_id."""
|
|
2640
2668
|
try:
|
|
2641
2669
|
from datetime import datetime
|
|
2670
|
+
# Sanitize text to prevent UTF-8 surrogate errors
|
|
2671
|
+
sanitized_title = registry._sanitize_text(title)
|
|
2672
|
+
sanitized_input = registry._sanitize_text(input_content)
|
|
2673
|
+
sanitized_output = registry._sanitize_text(output_content)
|
|
2674
|
+
|
|
2642
2675
|
conn = registry._get_notes_connection()
|
|
2643
2676
|
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
2644
2677
|
cursor = conn.execute('''
|
|
2645
2678
|
INSERT INTO notes (Created, Modified, Title, Input, Output)
|
|
2646
2679
|
VALUES (?, ?, ?, ?, ?)
|
|
2647
|
-
''', (now, now,
|
|
2680
|
+
''', (now, now, sanitized_title, sanitized_input, sanitized_output))
|
|
2648
2681
|
note_id = cursor.lastrowid
|
|
2649
2682
|
conn.commit()
|
|
2650
2683
|
conn.close()
|
package/mcp.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pomera-ai-commander",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.3",
|
|
4
4
|
"description": "Text processing toolkit with 22 MCP tools including case transformation, encoding, hashing, text analysis, and notes management for AI assistants.",
|
|
5
5
|
"homepage": "https://github.com/matbanik/Pomera-AI-Commander",
|
|
6
6
|
"repository": "https://github.com/matbanik/Pomera-AI-Commander",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pomera-ai-commander",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.3",
|
|
4
4
|
"description": "Text processing toolkit with 22 MCP tools for AI assistants - case transformation, encoding, hashing, text analysis, and notes management",
|
|
5
5
|
"main": "pomera_mcp_server.py",
|
|
6
6
|
"bin": {
|
package/pomera.py
CHANGED
|
@@ -1201,7 +1201,7 @@ class PromeraAIApp(tk.Tk):
|
|
|
1201
1201
|
pyproject_path = Path(__file__).parent / "pyproject.toml"
|
|
1202
1202
|
if pyproject_path.exists():
|
|
1203
1203
|
content = pyproject_path.read_text()
|
|
1204
|
-
match = re.search(r'version = "
|
|
1204
|
+
match = re.search(r'version = "1.2.3"]+)"', content)
|
|
1205
1205
|
if match:
|
|
1206
1206
|
current_version = match.group(1)
|
|
1207
1207
|
except Exception:
|
|
@@ -1212,7 +1212,7 @@ class PromeraAIApp(tk.Tk):
|
|
|
1212
1212
|
import importlib.metadata
|
|
1213
1213
|
current_version = importlib.metadata.version("pomera-ai-commander")
|
|
1214
1214
|
except Exception:
|
|
1215
|
-
current_version = "
|
|
1215
|
+
current_version = "1.2.3"
|
|
1216
1216
|
|
|
1217
1217
|
# Detect OS for download link
|
|
1218
1218
|
system = platform.system()
|
|
@@ -1350,11 +1350,8 @@ class PromeraAIApp(tk.Tk):
|
|
|
1350
1350
|
|
|
1351
1351
|
def _show_about_dialog(self):
|
|
1352
1352
|
"""Show About dialog."""
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
version = importlib.metadata.version("pomera-ai-commander")
|
|
1356
|
-
except Exception:
|
|
1357
|
-
version = "1.2.0"
|
|
1353
|
+
# Version managed by bump_version.py script
|
|
1354
|
+
version = "1.2.3"
|
|
1358
1355
|
|
|
1359
1356
|
messagebox.showinfo(
|
|
1360
1357
|
"About Pomera AI Commander",
|
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.2.
|
|
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.2.
|
|
163
|
+
server_version="1.2.3"
|
|
164
164
|
)
|
|
165
165
|
|
|
166
166
|
logger.info("Starting Pomera MCP Server...")
|
package/tools/notes_widget.py
CHANGED
|
@@ -99,6 +99,33 @@ class NotesWidget:
|
|
|
99
99
|
if conn:
|
|
100
100
|
conn.close()
|
|
101
101
|
|
|
102
|
+
def _sanitize_text(self, text: str) -> str:
|
|
103
|
+
"""
|
|
104
|
+
Sanitize text by removing invalid UTF-8 surrogate characters.
|
|
105
|
+
|
|
106
|
+
Lone surrogates (U+D800 to U+DFFF) are invalid in UTF-8 and cause
|
|
107
|
+
encoding errors when saving to the database. This can happen when
|
|
108
|
+
pasting content from the clipboard that contains malformed data.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
text: Input text that may contain invalid surrogates
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Sanitized text safe for UTF-8 encoding and database storage
|
|
115
|
+
"""
|
|
116
|
+
if not text:
|
|
117
|
+
return text
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
# This two-step process handles lone surrogates:
|
|
121
|
+
# 1. surrogatepass allows encoding surrogates (normally forbidden in UTF-8)
|
|
122
|
+
# 2. errors='replace' replaces invalid sequences with replacement char
|
|
123
|
+
sanitized = text.encode('utf-8', errors='surrogatepass').decode('utf-8', errors='replace')
|
|
124
|
+
return sanitized
|
|
125
|
+
except Exception:
|
|
126
|
+
# Fallback: manually filter out surrogate characters
|
|
127
|
+
return ''.join(c for c in text if not (0xD800 <= ord(c) <= 0xDFFF))
|
|
128
|
+
|
|
102
129
|
def init_database(self) -> None:
|
|
103
130
|
"""Initialize the SQLite database and Full-Text Search (FTS5) table."""
|
|
104
131
|
try:
|
|
@@ -707,10 +734,12 @@ class NotesWidget:
|
|
|
707
734
|
row = conn.execute('SELECT * FROM notes WHERE id = ?', (self.current_item,)).fetchone()
|
|
708
735
|
if row:
|
|
709
736
|
now = datetime.now().isoformat()
|
|
737
|
+
# Sanitize text to prevent UTF-8 surrogate errors
|
|
710
738
|
conn.execute('''
|
|
711
739
|
INSERT INTO notes (Created, Modified, Title, Input, Output)
|
|
712
740
|
VALUES (?, ?, ?, ?, ?)
|
|
713
|
-
''', (now, now, row['Title'],
|
|
741
|
+
''', (now, now, self._sanitize_text(row['Title']),
|
|
742
|
+
self._sanitize_text(row['Input']), self._sanitize_text(row['Output'])))
|
|
714
743
|
conn.commit()
|
|
715
744
|
self.perform_search(select_first=True)
|
|
716
745
|
self.logger.info(f"Duplicated note {self.current_item}")
|
|
@@ -788,9 +817,10 @@ class NotesWidget:
|
|
|
788
817
|
return
|
|
789
818
|
|
|
790
819
|
try:
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
820
|
+
# Sanitize text to prevent UTF-8 surrogate errors from clipboard paste
|
|
821
|
+
title = self._sanitize_text(self.title_entry.get() if hasattr(self, 'title_entry') else "")
|
|
822
|
+
input_content = self._sanitize_text(self.input_display.get(1.0, tk.END).strip())
|
|
823
|
+
output_content = self._sanitize_text(self.output_display.get(1.0, tk.END).strip())
|
|
794
824
|
|
|
795
825
|
now = datetime.now().isoformat()
|
|
796
826
|
|
|
@@ -962,12 +992,17 @@ class NotesWidget:
|
|
|
962
992
|
The ID of the created note, or None on error
|
|
963
993
|
"""
|
|
964
994
|
try:
|
|
995
|
+
# Sanitize text to prevent UTF-8 surrogate errors
|
|
996
|
+
sanitized_title = self._sanitize_text(title)
|
|
997
|
+
sanitized_input = self._sanitize_text(input_content)
|
|
998
|
+
sanitized_output = self._sanitize_text(output_content)
|
|
999
|
+
|
|
965
1000
|
now = datetime.now().isoformat()
|
|
966
1001
|
with self.get_db_connection() as conn:
|
|
967
1002
|
cursor = conn.execute('''
|
|
968
1003
|
INSERT INTO notes (Created, Modified, Title, Input, Output)
|
|
969
1004
|
VALUES (?, ?, ?, ?, ?)
|
|
970
|
-
''', (now, now,
|
|
1005
|
+
''', (now, now, sanitized_title, sanitized_input, sanitized_output))
|
|
971
1006
|
note_id = cursor.lastrowid
|
|
972
1007
|
conn.commit()
|
|
973
1008
|
|