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,43 +1,43 @@
1
- """
2
- MCP (Model Context Protocol) Module for Pomera AI Commander
3
-
4
- This module provides bidirectional MCP functionality:
5
- 1. MCP Client - Connect to external MCP servers (filesystem, GitHub, etc.)
6
- 2. MCP Server - Expose Pomera's text tools to external AI assistants
7
-
8
- Submodules:
9
- - schema: Data classes for MCP types (Tool, Resource, Message)
10
- - protocol: JSON-RPC 2.0 message handling
11
- - tool_registry: Maps Pomera tools to MCP tool definitions
12
- - server_stdio: stdio transport for MCP server
13
- - resource_provider: Exposes tab contents as MCP resources
14
- """
15
-
16
- from .schema import (
17
- MCPMessage,
18
- MCPTool,
19
- MCPResource,
20
- MCPError,
21
- MCPErrorCode,
22
- )
23
-
24
- from .protocol import MCPProtocol
25
-
26
- from .tool_registry import ToolRegistry, MCPToolAdapter
27
-
28
- __all__ = [
29
- # Schema
30
- "MCPMessage",
31
- "MCPTool",
32
- "MCPResource",
33
- "MCPError",
34
- "MCPErrorCode",
35
- # Protocol
36
- "MCPProtocol",
37
- # Tool Registry
38
- "ToolRegistry",
39
- "MCPToolAdapter",
40
- ]
41
-
42
- __version__ = "0.1.0"
43
-
1
+ """
2
+ MCP (Model Context Protocol) Module for Pomera AI Commander
3
+
4
+ This module provides bidirectional MCP functionality:
5
+ 1. MCP Client - Connect to external MCP servers (filesystem, GitHub, etc.)
6
+ 2. MCP Server - Expose Pomera's text tools to external AI assistants
7
+
8
+ Submodules:
9
+ - schema: Data classes for MCP types (Tool, Resource, Message)
10
+ - protocol: JSON-RPC 2.0 message handling
11
+ - tool_registry: Maps Pomera tools to MCP tool definitions
12
+ - server_stdio: stdio transport for MCP server
13
+ - resource_provider: Exposes tab contents as MCP resources
14
+ """
15
+
16
+ from .schema import (
17
+ MCPMessage,
18
+ MCPTool,
19
+ MCPResource,
20
+ MCPError,
21
+ MCPErrorCode,
22
+ )
23
+
24
+ from .protocol import MCPProtocol
25
+
26
+ from .tool_registry import ToolRegistry, MCPToolAdapter
27
+
28
+ __all__ = [
29
+ # Schema
30
+ "MCPMessage",
31
+ "MCPTool",
32
+ "MCPResource",
33
+ "MCPError",
34
+ "MCPErrorCode",
35
+ # Protocol
36
+ "MCPProtocol",
37
+ # Tool Registry
38
+ "ToolRegistry",
39
+ "MCPToolAdapter",
40
+ ]
41
+
42
+ __version__ = "0.1.0"
43
+
@@ -0,0 +1,334 @@
1
+ """
2
+ Find & Replace Diff MCP Tool
3
+
4
+ Provides regex find/replace with diff preview and automatic backup to Notes.
5
+ Designed for AI agent workflows requiring verification and rollback capability.
6
+
7
+ Operations:
8
+ - validate: Check regex syntax before use
9
+ - preview: Show unified diff of proposed changes
10
+ - execute: Perform replacement with automatic backup to Notes
11
+ - recall: Retrieve previous operation state for rollback
12
+ """
13
+
14
+ import re
15
+ import json
16
+ import logging
17
+ from typing import Dict, Any, Optional, List, Tuple
18
+ from dataclasses import dataclass, asdict
19
+ from datetime import datetime
20
+
21
+ # Import diff utilities
22
+ try:
23
+ from core.diff_utils import generate_find_replace_preview, generate_compact_diff, FindReplacePreview
24
+ DIFF_UTILS_AVAILABLE = True
25
+ except ImportError:
26
+ DIFF_UTILS_AVAILABLE = False
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+
31
+ @dataclass
32
+ class FindReplaceOperation:
33
+ """Represents a find/replace operation for storage in Notes."""
34
+ find_pattern: str
35
+ replace_pattern: str
36
+ flags: List[str]
37
+ original_text: str
38
+ modified_text: str
39
+ match_count: int
40
+ timestamp: str
41
+
42
+ def to_json(self) -> str:
43
+ return json.dumps(asdict(self), ensure_ascii=False)
44
+
45
+ @classmethod
46
+ def from_json(cls, json_str: str) -> 'FindReplaceOperation':
47
+ data = json.loads(json_str)
48
+ return cls(**data)
49
+
50
+
51
+ def validate_regex(pattern: str, flags: List[str] = None) -> Dict[str, Any]:
52
+ """
53
+ Validate a regex pattern.
54
+
55
+ Args:
56
+ pattern: Regex pattern string
57
+ flags: Optional list of flag characters ('i', 'm', 's', 'x')
58
+
59
+ Returns:
60
+ Dict with validation result
61
+ """
62
+ if not pattern:
63
+ return {"valid": True, "pattern": "", "groups": 0, "flags_applied": []}
64
+
65
+ try:
66
+ # Convert flag characters to re flags
67
+ re_flags = 0
68
+ flags_applied = []
69
+ if flags:
70
+ flag_map = {
71
+ 'i': (re.IGNORECASE, 'IGNORECASE'),
72
+ 'm': (re.MULTILINE, 'MULTILINE'),
73
+ 's': (re.DOTALL, 'DOTALL'),
74
+ 'x': (re.VERBOSE, 'VERBOSE')
75
+ }
76
+ for f in flags:
77
+ if f.lower() in flag_map:
78
+ re_flags |= flag_map[f.lower()][0]
79
+ flags_applied.append(flag_map[f.lower()][1])
80
+
81
+ compiled = re.compile(pattern, re_flags)
82
+ return {
83
+ "valid": True,
84
+ "pattern": pattern,
85
+ "groups": compiled.groups,
86
+ "flags_applied": flags_applied
87
+ }
88
+ except re.error as e:
89
+ suggestion = _get_regex_suggestion(str(e))
90
+ return {
91
+ "valid": False,
92
+ "pattern": pattern,
93
+ "error": str(e),
94
+ "suggestion": suggestion
95
+ }
96
+
97
+
98
+ def preview_replace(
99
+ text: str,
100
+ find_pattern: str,
101
+ replace_pattern: str,
102
+ flags: List[str] = None,
103
+ context_lines: int = 2,
104
+ max_diff_lines: int = 50
105
+ ) -> Dict[str, Any]:
106
+ """
107
+ Generate a preview of find/replace operation with compact diff.
108
+
109
+ Args:
110
+ text: Input text to process
111
+ find_pattern: Regex pattern to find
112
+ replace_pattern: Replacement string
113
+ flags: Optional regex flags
114
+ context_lines: Lines of context in diff
115
+ max_diff_lines: Maximum diff lines to return (token efficiency)
116
+
117
+ Returns:
118
+ Dict with preview information
119
+ """
120
+ # Validate first
121
+ validation = validate_regex(find_pattern, flags)
122
+ if not validation["valid"]:
123
+ return {"success": False, "error": validation["error"], "suggestion": validation.get("suggestion", "")}
124
+
125
+ # Build regex flags
126
+ re_flags = 0
127
+ if flags:
128
+ flag_map = {'i': re.IGNORECASE, 'm': re.MULTILINE, 's': re.DOTALL, 'x': re.VERBOSE}
129
+ for f in flags:
130
+ if f.lower() in flag_map:
131
+ re_flags |= flag_map[f.lower()]
132
+
133
+ try:
134
+ pattern = re.compile(find_pattern, re_flags)
135
+ matches = list(pattern.finditer(text))
136
+
137
+ if not matches:
138
+ return {
139
+ "success": True,
140
+ "match_count": 0,
141
+ "diff": "No matches found.",
142
+ "lines_affected": 0
143
+ }
144
+
145
+ # Perform replacement
146
+ modified_text = pattern.sub(replace_pattern, text)
147
+
148
+ # Generate compact diff (token-efficient)
149
+ diff = generate_compact_diff(text, modified_text, max_lines=max_diff_lines) if DIFF_UTILS_AVAILABLE else _basic_diff(text, modified_text)
150
+
151
+ # Count affected lines
152
+ lines_affected = len(set(text[:m.start()].count('\n') + 1 for m in matches))
153
+
154
+ return {
155
+ "success": True,
156
+ "match_count": len(matches),
157
+ "lines_affected": lines_affected,
158
+ "diff": diff
159
+ }
160
+ except Exception as e:
161
+ return {"success": False, "error": str(e)}
162
+
163
+
164
+ def execute_replace(
165
+ text: str,
166
+ find_pattern: str,
167
+ replace_pattern: str,
168
+ flags: List[str] = None,
169
+ save_to_notes: bool = True,
170
+ notes_handler = None
171
+ ) -> Dict[str, Any]:
172
+ """
173
+ Execute find/replace with optional backup to Notes.
174
+
175
+ Args:
176
+ text: Input text to process
177
+ find_pattern: Regex pattern to find
178
+ replace_pattern: Replacement string
179
+ flags: Optional regex flags
180
+ save_to_notes: Whether to save operation to Notes for rollback
181
+ notes_handler: Function to save to notes (called as notes_handler(title, input_content, output_content))
182
+
183
+ Returns:
184
+ Dict with execution result including note_id if saved
185
+ """
186
+ # Validate first
187
+ validation = validate_regex(find_pattern, flags)
188
+ if not validation["valid"]:
189
+ return {"success": False, "error": validation["error"]}
190
+
191
+ # Build regex flags
192
+ re_flags = 0
193
+ if flags:
194
+ flag_map = {'i': re.IGNORECASE, 'm': re.MULTILINE, 's': re.DOTALL, 'x': re.VERBOSE}
195
+ for f in flags:
196
+ if f.lower() in flag_map:
197
+ re_flags |= flag_map[f.lower()]
198
+
199
+ try:
200
+ pattern = re.compile(find_pattern, re_flags)
201
+ matches = list(pattern.finditer(text))
202
+
203
+ if not matches:
204
+ return {
205
+ "success": True,
206
+ "replacements": 0,
207
+ "modified_text": text,
208
+ "note_id": None
209
+ }
210
+
211
+ # Perform replacement
212
+ modified_text = pattern.sub(replace_pattern, text)
213
+
214
+ # Count affected lines
215
+ lines_affected = len(set(text[:m.start()].count('\n') + 1 for m in matches))
216
+
217
+ result = {
218
+ "success": True,
219
+ "replacements": len(matches),
220
+ "lines_affected": lines_affected,
221
+ "modified_text": modified_text,
222
+ "note_id": None
223
+ }
224
+
225
+ # Save to notes if requested
226
+ if save_to_notes and notes_handler:
227
+ try:
228
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
229
+ title = f"FindReplace/{timestamp}"
230
+
231
+ # Create operation record
232
+ operation = FindReplaceOperation(
233
+ find_pattern=find_pattern,
234
+ replace_pattern=replace_pattern,
235
+ flags=flags or [],
236
+ original_text=text,
237
+ modified_text=modified_text,
238
+ match_count=len(matches),
239
+ timestamp=timestamp
240
+ )
241
+
242
+ note_id = notes_handler(
243
+ title=title,
244
+ input_content=text,
245
+ output_content=operation.to_json()
246
+ )
247
+ result["note_id"] = note_id
248
+ result["note_title"] = title
249
+ except Exception as e:
250
+ logger.warning(f"Failed to save operation to notes: {e}")
251
+ result["note_error"] = str(e)
252
+
253
+ return result
254
+ except Exception as e:
255
+ return {"success": False, "error": str(e)}
256
+
257
+
258
+ def recall_operation(note_id: int, notes_getter = None) -> Dict[str, Any]:
259
+ """
260
+ Recall a previous find/replace operation from Notes.
261
+
262
+ Args:
263
+ note_id: ID of the note to recall
264
+ notes_getter: Function to get note by ID (returns dict with 'output_content')
265
+
266
+ Returns:
267
+ Dict with recalled operation details
268
+ """
269
+ if not notes_getter:
270
+ return {"success": False, "error": "Notes getter not available"}
271
+
272
+ try:
273
+ note = notes_getter(note_id)
274
+ if not note:
275
+ return {"success": False, "error": f"Note {note_id} not found"}
276
+
277
+ # Parse the operation from output_content
278
+ operation = FindReplaceOperation.from_json(note.get('output_content', '{}'))
279
+
280
+ return {
281
+ "success": True,
282
+ "note_id": note_id,
283
+ "title": note.get('title', ''),
284
+ "find_pattern": operation.find_pattern,
285
+ "replace_pattern": operation.replace_pattern,
286
+ "flags": operation.flags,
287
+ "original_text": operation.original_text,
288
+ "modified_text": operation.modified_text,
289
+ "match_count": operation.match_count,
290
+ "timestamp": operation.timestamp
291
+ }
292
+ except Exception as e:
293
+ return {"success": False, "error": str(e)}
294
+
295
+
296
+ def _get_regex_suggestion(error_msg: str) -> str:
297
+ """Get helpful suggestion for common regex errors."""
298
+ suggestions = {
299
+ "unterminated": "Check for unmatched parentheses, brackets, or quotes",
300
+ "unbalanced": "Ensure all opening ( [ { have matching closing ) ] }",
301
+ "nothing to repeat": "Quantifiers like * + ? need something before them",
302
+ "bad escape": "Use double backslash \\\\ or raw string r'' for special chars",
303
+ "look-behind": "Python look-behind requires fixed-width pattern",
304
+ "bad character range": "Check character ranges like [a-z], ensure start < end"
305
+ }
306
+
307
+ error_lower = error_msg.lower()
308
+ for key, suggestion in suggestions.items():
309
+ if key in error_lower:
310
+ return suggestion
311
+
312
+ return "Check regex syntax - see Python re module documentation"
313
+
314
+
315
+ def _basic_diff(original: str, modified: str) -> str:
316
+ """Basic diff when diff_utils not available."""
317
+ orig_lines = original.splitlines()
318
+ mod_lines = modified.splitlines()
319
+
320
+ output = []
321
+ for i, (o, m) in enumerate(zip(orig_lines, mod_lines)):
322
+ if o != m:
323
+ output.append(f"-{i+1}: {o}")
324
+ output.append(f"+{i+1}: {m}")
325
+
326
+ # Handle length differences
327
+ if len(mod_lines) > len(orig_lines):
328
+ for i in range(len(orig_lines), len(mod_lines)):
329
+ output.append(f"+{i+1}: {mod_lines[i]}")
330
+ elif len(orig_lines) > len(mod_lines):
331
+ for i in range(len(mod_lines), len(orig_lines)):
332
+ output.append(f"-{i+1}: {orig_lines[i]}")
333
+
334
+ return '\n'.join(output) if output else "No differences"