connectonion 0.5.8__py3-none-any.whl
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.
- connectonion/__init__.py +78 -0
- connectonion/address.py +320 -0
- connectonion/agent.py +450 -0
- connectonion/announce.py +84 -0
- connectonion/asgi.py +287 -0
- connectonion/auto_debug_exception.py +181 -0
- connectonion/cli/__init__.py +3 -0
- connectonion/cli/browser_agent/__init__.py +5 -0
- connectonion/cli/browser_agent/browser.py +243 -0
- connectonion/cli/browser_agent/prompt.md +107 -0
- connectonion/cli/commands/__init__.py +1 -0
- connectonion/cli/commands/auth_commands.py +527 -0
- connectonion/cli/commands/browser_commands.py +27 -0
- connectonion/cli/commands/create.py +511 -0
- connectonion/cli/commands/deploy_commands.py +220 -0
- connectonion/cli/commands/doctor_commands.py +173 -0
- connectonion/cli/commands/init.py +469 -0
- connectonion/cli/commands/project_cmd_lib.py +828 -0
- connectonion/cli/commands/reset_commands.py +149 -0
- connectonion/cli/commands/status_commands.py +168 -0
- connectonion/cli/docs/co-vibecoding-principles-docs-contexts-all-in-one.md +2010 -0
- connectonion/cli/docs/connectonion.md +1256 -0
- connectonion/cli/docs.md +123 -0
- connectonion/cli/main.py +148 -0
- connectonion/cli/templates/meta-agent/README.md +287 -0
- connectonion/cli/templates/meta-agent/agent.py +196 -0
- connectonion/cli/templates/meta-agent/prompts/answer_prompt.md +9 -0
- connectonion/cli/templates/meta-agent/prompts/docs_retrieve_prompt.md +15 -0
- connectonion/cli/templates/meta-agent/prompts/metagent.md +71 -0
- connectonion/cli/templates/meta-agent/prompts/think_prompt.md +18 -0
- connectonion/cli/templates/minimal/README.md +56 -0
- connectonion/cli/templates/minimal/agent.py +40 -0
- connectonion/cli/templates/playwright/README.md +118 -0
- connectonion/cli/templates/playwright/agent.py +336 -0
- connectonion/cli/templates/playwright/prompt.md +102 -0
- connectonion/cli/templates/playwright/requirements.txt +3 -0
- connectonion/cli/templates/web-research/agent.py +122 -0
- connectonion/connect.py +128 -0
- connectonion/console.py +539 -0
- connectonion/debug_agent/__init__.py +13 -0
- connectonion/debug_agent/agent.py +45 -0
- connectonion/debug_agent/prompts/debug_assistant.md +72 -0
- connectonion/debug_agent/runtime_inspector.py +406 -0
- connectonion/debug_explainer/__init__.py +10 -0
- connectonion/debug_explainer/explain_agent.py +114 -0
- connectonion/debug_explainer/explain_context.py +263 -0
- connectonion/debug_explainer/explainer_prompt.md +29 -0
- connectonion/debug_explainer/root_cause_analysis_prompt.md +43 -0
- connectonion/debugger_ui.py +1039 -0
- connectonion/decorators.py +208 -0
- connectonion/events.py +248 -0
- connectonion/execution_analyzer/__init__.py +9 -0
- connectonion/execution_analyzer/execution_analysis.py +93 -0
- connectonion/execution_analyzer/execution_analysis_prompt.md +47 -0
- connectonion/host.py +579 -0
- connectonion/interactive_debugger.py +342 -0
- connectonion/llm.py +801 -0
- connectonion/llm_do.py +307 -0
- connectonion/logger.py +300 -0
- connectonion/prompt_files/__init__.py +1 -0
- connectonion/prompt_files/analyze_contact.md +62 -0
- connectonion/prompt_files/eval_expected.md +12 -0
- connectonion/prompt_files/react_evaluate.md +11 -0
- connectonion/prompt_files/react_plan.md +16 -0
- connectonion/prompt_files/reflect.md +22 -0
- connectonion/prompts.py +144 -0
- connectonion/relay.py +200 -0
- connectonion/static/docs.html +688 -0
- connectonion/tool_executor.py +279 -0
- connectonion/tool_factory.py +186 -0
- connectonion/tool_registry.py +105 -0
- connectonion/trust.py +166 -0
- connectonion/trust_agents.py +71 -0
- connectonion/trust_functions.py +88 -0
- connectonion/tui/__init__.py +57 -0
- connectonion/tui/divider.py +39 -0
- connectonion/tui/dropdown.py +251 -0
- connectonion/tui/footer.py +31 -0
- connectonion/tui/fuzzy.py +56 -0
- connectonion/tui/input.py +278 -0
- connectonion/tui/keys.py +35 -0
- connectonion/tui/pick.py +130 -0
- connectonion/tui/providers.py +155 -0
- connectonion/tui/status_bar.py +163 -0
- connectonion/usage.py +161 -0
- connectonion/useful_events_handlers/__init__.py +16 -0
- connectonion/useful_events_handlers/reflect.py +116 -0
- connectonion/useful_plugins/__init__.py +20 -0
- connectonion/useful_plugins/calendar_plugin.py +163 -0
- connectonion/useful_plugins/eval.py +139 -0
- connectonion/useful_plugins/gmail_plugin.py +162 -0
- connectonion/useful_plugins/image_result_formatter.py +127 -0
- connectonion/useful_plugins/re_act.py +78 -0
- connectonion/useful_plugins/shell_approval.py +159 -0
- connectonion/useful_tools/__init__.py +44 -0
- connectonion/useful_tools/diff_writer.py +192 -0
- connectonion/useful_tools/get_emails.py +183 -0
- connectonion/useful_tools/gmail.py +1596 -0
- connectonion/useful_tools/google_calendar.py +613 -0
- connectonion/useful_tools/memory.py +380 -0
- connectonion/useful_tools/microsoft_calendar.py +604 -0
- connectonion/useful_tools/outlook.py +488 -0
- connectonion/useful_tools/send_email.py +205 -0
- connectonion/useful_tools/shell.py +97 -0
- connectonion/useful_tools/slash_command.py +201 -0
- connectonion/useful_tools/terminal.py +285 -0
- connectonion/useful_tools/todo_list.py +241 -0
- connectonion/useful_tools/web_fetch.py +216 -0
- connectonion/xray.py +467 -0
- connectonion-0.5.8.dist-info/METADATA +741 -0
- connectonion-0.5.8.dist-info/RECORD +113 -0
- connectonion-0.5.8.dist-info/WHEEL +4 -0
- connectonion-0.5.8.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Persistent key-value memory storage for agents using markdown files with automatic scaling
|
|
3
|
+
LLM-Note:
|
|
4
|
+
Dependencies: imports from [os, re] | imported by [useful_tools/__init__.py] | tested by [tests/test_memory.py]
|
|
5
|
+
Data flow: Agent calls Memory methods → write_memory(key, content) stores in memory.md (or directory) → read_memory(key) retrieves by parsing markdown sections → list_memories() shows all keys → search_memory(pattern) uses regex across all content → auto-splits to directory when file exceeds split_threshold lines
|
|
6
|
+
State/Effects: creates/modifies memory.md file or memory/ directory | single file uses ## headings as keys | auto-migrates to directory structure at threshold | does NOT delete old file on migration (removes it) | no network I/O
|
|
7
|
+
Integration: exposes Memory class with write_memory(), read_memory(), list_memories(), search_memory() | used as agent tool by passing to Agent(tools=[memory]) | storage format: markdown with ## key headings | directory format: one .md file per key
|
|
8
|
+
Performance: file I/O per operation (no caching) | parsing is O(n) file lines | search is O(n*m) files * lines | auto-scaling avoids single file bloat
|
|
9
|
+
Errors: returns error string for missing keys | invalid key names sanitized (alphanumeric, hyphen, underscore only) | no exceptions raised (returns error messages)
|
|
10
|
+
|
|
11
|
+
**Simple by default**: Stores all memories in a single `memory.md` file.
|
|
12
|
+
**Scales automatically**: Splits into directory when file exceeds 3000 lines.
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
from connectonion import Agent, Memory
|
|
16
|
+
|
|
17
|
+
memory = Memory() # Creates memory.md
|
|
18
|
+
agent = Agent("assistant", tools=[memory])
|
|
19
|
+
|
|
20
|
+
# Agent can now use:
|
|
21
|
+
# - write_memory(key, content)
|
|
22
|
+
# - read_memory(key)
|
|
23
|
+
# - list_memories()
|
|
24
|
+
# - search_memory(pattern) # Regex pattern
|
|
25
|
+
|
|
26
|
+
Example:
|
|
27
|
+
from connectonion import Agent, Memory
|
|
28
|
+
|
|
29
|
+
memory = Memory()
|
|
30
|
+
agent = Agent(
|
|
31
|
+
name="assistant",
|
|
32
|
+
system_prompt="You are a helpful assistant with persistent memory.",
|
|
33
|
+
tools=[memory]
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
agent.input("Remember that Alice prefers email over phone calls")
|
|
37
|
+
agent.input("What do I know about Alice?")
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
import os
|
|
41
|
+
import re
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Memory:
|
|
45
|
+
"""Simple memory system - single file by default, auto-scales to directory."""
|
|
46
|
+
|
|
47
|
+
def __init__(self, memory_file: str = None, memory_dir: str = None, split_threshold: int = 3000):
|
|
48
|
+
"""Initialize memory system.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
memory_file: Path to memory file (default: "memory.md")
|
|
52
|
+
memory_dir: Directory for memories (backward compatibility, creates dir immediately)
|
|
53
|
+
split_threshold: Line count that triggers directory split (default: 3000)
|
|
54
|
+
"""
|
|
55
|
+
self.split_threshold = split_threshold
|
|
56
|
+
self.using_directory = False
|
|
57
|
+
|
|
58
|
+
# Handle backward compatibility with memory_dir parameter
|
|
59
|
+
if memory_dir is not None:
|
|
60
|
+
self.memory_dir = memory_dir
|
|
61
|
+
self.memory_file = f"{memory_dir}.md"
|
|
62
|
+
os.makedirs(memory_dir, exist_ok=True)
|
|
63
|
+
self.using_directory = True
|
|
64
|
+
elif memory_file is None:
|
|
65
|
+
self.memory_file = "memory.md"
|
|
66
|
+
# Check if already using directory structure
|
|
67
|
+
dir_path = self.memory_file.replace('.md', '')
|
|
68
|
+
if os.path.isdir(dir_path):
|
|
69
|
+
self.using_directory = True
|
|
70
|
+
self.memory_dir = dir_path
|
|
71
|
+
else:
|
|
72
|
+
self.memory_file = memory_file
|
|
73
|
+
# Check if already using directory structure
|
|
74
|
+
dir_path = memory_file.replace('.md', '')
|
|
75
|
+
if os.path.isdir(dir_path):
|
|
76
|
+
self.using_directory = True
|
|
77
|
+
self.memory_dir = dir_path
|
|
78
|
+
|
|
79
|
+
def write_memory(self, key: str, content: str) -> str:
|
|
80
|
+
"""Write content to memory.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
key: Memory key/name
|
|
84
|
+
content: Content to write (supports markdown)
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Confirmation message
|
|
88
|
+
"""
|
|
89
|
+
safe_key = "".join(c for c in key if c.isalnum() or c in ('-', '_')).lower()
|
|
90
|
+
if not safe_key:
|
|
91
|
+
return "Invalid key name. Use alphanumeric characters, hyphens, or underscores."
|
|
92
|
+
|
|
93
|
+
if self.using_directory:
|
|
94
|
+
return self._write_directory(safe_key, content)
|
|
95
|
+
|
|
96
|
+
return self._write_single_file(safe_key, content)
|
|
97
|
+
|
|
98
|
+
def read_memory(self, key: str) -> str:
|
|
99
|
+
"""Read content from memory.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
key: Memory key/name to read
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Memory content or error message
|
|
106
|
+
"""
|
|
107
|
+
safe_key = "".join(c for c in key if c.isalnum() or c in ('-', '_')).lower()
|
|
108
|
+
|
|
109
|
+
if self.using_directory:
|
|
110
|
+
return self._read_directory(safe_key)
|
|
111
|
+
|
|
112
|
+
return self._read_single_file(safe_key)
|
|
113
|
+
|
|
114
|
+
def list_memories(self) -> str:
|
|
115
|
+
"""List all stored memories.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Formatted list of available memory keys
|
|
119
|
+
"""
|
|
120
|
+
if self.using_directory:
|
|
121
|
+
return self._list_directory()
|
|
122
|
+
|
|
123
|
+
return self._list_single_file()
|
|
124
|
+
|
|
125
|
+
def search_memory(self, pattern: str) -> str:
|
|
126
|
+
"""Search across all memories using regex pattern.
|
|
127
|
+
|
|
128
|
+
Use regex flags in pattern: (?i) for case-insensitive
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
pattern: Regex pattern to search for
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
Formatted search results showing matching memories and lines
|
|
135
|
+
"""
|
|
136
|
+
if self.using_directory:
|
|
137
|
+
return self._search_directory(pattern)
|
|
138
|
+
|
|
139
|
+
return self._search_single_file(pattern)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# Single file implementation
|
|
143
|
+
def _write_single_file(self, key: str, content: str) -> str:
|
|
144
|
+
"""Write to single memory file."""
|
|
145
|
+
if not os.path.exists(self.memory_file):
|
|
146
|
+
# Create new file
|
|
147
|
+
with open(self.memory_file, 'w') as f:
|
|
148
|
+
f.write(f"## {key}\n\n{content}\n\n")
|
|
149
|
+
return f"Memory saved: {key}"
|
|
150
|
+
|
|
151
|
+
# Read and parse existing file
|
|
152
|
+
with open(self.memory_file, 'r') as f:
|
|
153
|
+
file_content = f.read()
|
|
154
|
+
|
|
155
|
+
sections = self._parse_sections(file_content)
|
|
156
|
+
sections[key] = content
|
|
157
|
+
|
|
158
|
+
# Write back
|
|
159
|
+
new_content = self._serialize_sections(sections)
|
|
160
|
+
with open(self.memory_file, 'w') as f:
|
|
161
|
+
f.write(new_content)
|
|
162
|
+
|
|
163
|
+
# Check if we need to split
|
|
164
|
+
line_count = new_content.count('\n')
|
|
165
|
+
if line_count > self.split_threshold:
|
|
166
|
+
self._split_to_directory(sections)
|
|
167
|
+
return f"Memory saved: {key} (migrated to directory)"
|
|
168
|
+
|
|
169
|
+
return f"Memory saved: {key}"
|
|
170
|
+
|
|
171
|
+
def _read_single_file(self, key: str) -> str:
|
|
172
|
+
"""Read from single memory file."""
|
|
173
|
+
if not os.path.exists(self.memory_file):
|
|
174
|
+
return f"Memory not found: {key}\nNo memories stored yet"
|
|
175
|
+
|
|
176
|
+
with open(self.memory_file, 'r') as f:
|
|
177
|
+
file_content = f.read()
|
|
178
|
+
|
|
179
|
+
sections = self._parse_sections(file_content)
|
|
180
|
+
|
|
181
|
+
if key not in sections:
|
|
182
|
+
available = ", ".join(sorted(sections.keys())) if sections else "none"
|
|
183
|
+
return f"Memory not found: {key}\nAvailable memories: {available}"
|
|
184
|
+
|
|
185
|
+
return f"Memory: {key}\n\n{sections[key]}"
|
|
186
|
+
|
|
187
|
+
def _list_single_file(self) -> str:
|
|
188
|
+
"""List memories from single file."""
|
|
189
|
+
if not os.path.exists(self.memory_file):
|
|
190
|
+
return "No memories stored yet"
|
|
191
|
+
|
|
192
|
+
with open(self.memory_file, 'r') as f:
|
|
193
|
+
file_content = f.read()
|
|
194
|
+
|
|
195
|
+
sections = self._parse_sections(file_content)
|
|
196
|
+
|
|
197
|
+
if not sections:
|
|
198
|
+
return "No memories stored yet"
|
|
199
|
+
|
|
200
|
+
output = [f"Stored Memories ({len(sections)}):"]
|
|
201
|
+
for i, key in enumerate(sorted(sections.keys()), 1):
|
|
202
|
+
size = len(sections[key])
|
|
203
|
+
output.append(f"{i}. {key} ({size} bytes)")
|
|
204
|
+
|
|
205
|
+
return "\n".join(output)
|
|
206
|
+
|
|
207
|
+
def _search_single_file(self, pattern: str) -> str:
|
|
208
|
+
"""Search in single memory file."""
|
|
209
|
+
if not os.path.exists(self.memory_file):
|
|
210
|
+
return "No memories to search"
|
|
211
|
+
|
|
212
|
+
with open(self.memory_file, 'r') as f:
|
|
213
|
+
file_content = f.read()
|
|
214
|
+
|
|
215
|
+
sections = self._parse_sections(file_content)
|
|
216
|
+
|
|
217
|
+
if not sections:
|
|
218
|
+
return "No memories to search"
|
|
219
|
+
|
|
220
|
+
regex = re.compile(pattern)
|
|
221
|
+
results = []
|
|
222
|
+
total_matches = 0
|
|
223
|
+
|
|
224
|
+
for key in sorted(sections.keys()):
|
|
225
|
+
content = sections[key]
|
|
226
|
+
lines = content.split('\n')
|
|
227
|
+
|
|
228
|
+
matches = []
|
|
229
|
+
for line_num, line in enumerate(lines, 1):
|
|
230
|
+
if regex.search(line):
|
|
231
|
+
matches.append((line_num, line.strip()))
|
|
232
|
+
|
|
233
|
+
if matches:
|
|
234
|
+
total_matches += len(matches)
|
|
235
|
+
results.append(f"\n{key}:")
|
|
236
|
+
for line_num, line in matches:
|
|
237
|
+
results.append(f" Line {line_num}: {line}")
|
|
238
|
+
|
|
239
|
+
if not results:
|
|
240
|
+
return f"No matches found for pattern: {pattern}"
|
|
241
|
+
|
|
242
|
+
output = [f"Search Results ({total_matches} matches):"]
|
|
243
|
+
output.extend(results)
|
|
244
|
+
return "\n".join(output)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _parse_sections(self, content: str) -> dict:
|
|
248
|
+
"""Parse memory.md into sections."""
|
|
249
|
+
sections = {}
|
|
250
|
+
current_key = None
|
|
251
|
+
current_content = []
|
|
252
|
+
|
|
253
|
+
for line in content.split('\n'):
|
|
254
|
+
if line.startswith('## '):
|
|
255
|
+
# Save previous section
|
|
256
|
+
if current_key:
|
|
257
|
+
sections[current_key] = '\n'.join(current_content).strip()
|
|
258
|
+
# Start new section
|
|
259
|
+
current_key = line[3:].strip()
|
|
260
|
+
current_content = []
|
|
261
|
+
elif current_key:
|
|
262
|
+
current_content.append(line)
|
|
263
|
+
|
|
264
|
+
# Save last section
|
|
265
|
+
if current_key:
|
|
266
|
+
sections[current_key] = '\n'.join(current_content).strip()
|
|
267
|
+
|
|
268
|
+
return sections
|
|
269
|
+
|
|
270
|
+
def _serialize_sections(self, sections: dict) -> str:
|
|
271
|
+
"""Serialize sections back to memory.md format."""
|
|
272
|
+
output = []
|
|
273
|
+
for key in sorted(sections.keys()):
|
|
274
|
+
output.append(f"## {key}\n")
|
|
275
|
+
output.append(f"{sections[key]}\n")
|
|
276
|
+
return '\n'.join(output)
|
|
277
|
+
|
|
278
|
+
def _split_to_directory(self, sections: dict):
|
|
279
|
+
"""Split single file into directory structure."""
|
|
280
|
+
self.memory_dir = self.memory_file.replace('.md', '')
|
|
281
|
+
os.makedirs(self.memory_dir, exist_ok=True)
|
|
282
|
+
|
|
283
|
+
# Write each section to its own file
|
|
284
|
+
for key, content in sections.items():
|
|
285
|
+
filepath = os.path.join(self.memory_dir, f"{key}.md")
|
|
286
|
+
with open(filepath, 'w') as f:
|
|
287
|
+
f.write(content)
|
|
288
|
+
|
|
289
|
+
# Remove single file
|
|
290
|
+
if os.path.exists(self.memory_file):
|
|
291
|
+
os.remove(self.memory_file)
|
|
292
|
+
|
|
293
|
+
self.using_directory = True
|
|
294
|
+
|
|
295
|
+
# Directory implementation
|
|
296
|
+
def _write_directory(self, key: str, content: str) -> str:
|
|
297
|
+
"""Write to directory structure."""
|
|
298
|
+
os.makedirs(self.memory_dir, exist_ok=True)
|
|
299
|
+
filepath = os.path.join(self.memory_dir, f"{key}.md")
|
|
300
|
+
with open(filepath, 'w') as f:
|
|
301
|
+
f.write(content)
|
|
302
|
+
return f"Memory saved: {key}"
|
|
303
|
+
|
|
304
|
+
def _read_directory(self, key: str) -> str:
|
|
305
|
+
"""Read from directory structure."""
|
|
306
|
+
if not os.path.exists(self.memory_dir):
|
|
307
|
+
return f"Memory not found: {key}\nNo memories stored yet"
|
|
308
|
+
|
|
309
|
+
filepath = os.path.join(self.memory_dir, f"{key}.md")
|
|
310
|
+
|
|
311
|
+
if not os.path.exists(filepath):
|
|
312
|
+
files = [f.replace('.md', '') for f in os.listdir(self.memory_dir) if f.endswith('.md')]
|
|
313
|
+
available = ", ".join(sorted(files)) if files else "none"
|
|
314
|
+
return f"Memory not found: {key}\nAvailable memories: {available}"
|
|
315
|
+
|
|
316
|
+
with open(filepath, 'r') as f:
|
|
317
|
+
content = f.read()
|
|
318
|
+
|
|
319
|
+
return f"Memory: {key}\n\n{content}"
|
|
320
|
+
|
|
321
|
+
def _list_directory(self) -> str:
|
|
322
|
+
"""List memories from directory."""
|
|
323
|
+
if not os.path.exists(self.memory_dir):
|
|
324
|
+
return "No memories stored yet"
|
|
325
|
+
|
|
326
|
+
files = [f for f in os.listdir(self.memory_dir) if f.endswith('.md')]
|
|
327
|
+
|
|
328
|
+
if not files:
|
|
329
|
+
return "No memories stored yet"
|
|
330
|
+
|
|
331
|
+
keys = [f.replace('.md', '') for f in files]
|
|
332
|
+
output = [f"Stored Memories ({len(keys)}):"]
|
|
333
|
+
|
|
334
|
+
for i, key in enumerate(sorted(keys), 1):
|
|
335
|
+
filepath = os.path.join(self.memory_dir, f"{key}.md")
|
|
336
|
+
size = os.path.getsize(filepath)
|
|
337
|
+
output.append(f"{i}. {key} ({size} bytes)")
|
|
338
|
+
|
|
339
|
+
return "\n".join(output)
|
|
340
|
+
|
|
341
|
+
def _search_directory(self, pattern: str) -> str:
|
|
342
|
+
"""Search in directory structure."""
|
|
343
|
+
if not os.path.exists(self.memory_dir):
|
|
344
|
+
return "No memories to search"
|
|
345
|
+
|
|
346
|
+
files = [f for f in os.listdir(self.memory_dir) if f.endswith('.md')]
|
|
347
|
+
|
|
348
|
+
if not files:
|
|
349
|
+
return "No memories to search"
|
|
350
|
+
|
|
351
|
+
regex = re.compile(pattern)
|
|
352
|
+
results = []
|
|
353
|
+
total_matches = 0
|
|
354
|
+
|
|
355
|
+
for filename in sorted(files):
|
|
356
|
+
key = filename.replace('.md', '')
|
|
357
|
+
filepath = os.path.join(self.memory_dir, filename)
|
|
358
|
+
|
|
359
|
+
with open(filepath, 'r') as f:
|
|
360
|
+
content = f.read()
|
|
361
|
+
lines = content.split('\n')
|
|
362
|
+
|
|
363
|
+
matches = []
|
|
364
|
+
for line_num, line in enumerate(lines, 1):
|
|
365
|
+
if regex.search(line):
|
|
366
|
+
matches.append((line_num, line.strip()))
|
|
367
|
+
|
|
368
|
+
if matches:
|
|
369
|
+
total_matches += len(matches)
|
|
370
|
+
results.append(f"\n{key}:")
|
|
371
|
+
for line_num, line in matches:
|
|
372
|
+
results.append(f" Line {line_num}: {line}")
|
|
373
|
+
|
|
374
|
+
if not results:
|
|
375
|
+
return f"No matches found for pattern: {pattern}"
|
|
376
|
+
|
|
377
|
+
output = [f"Search Results ({total_matches} matches):"]
|
|
378
|
+
output.extend(results)
|
|
379
|
+
return "\n".join(output)
|
|
380
|
+
|