basic-memory 0.7.0__py3-none-any.whl → 0.16.1__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.

Potentially problematic release.


This version of basic-memory might be problematic. Click here for more details.

Files changed (150) hide show
  1. basic_memory/__init__.py +5 -1
  2. basic_memory/alembic/alembic.ini +119 -0
  3. basic_memory/alembic/env.py +27 -3
  4. basic_memory/alembic/migrations.py +4 -9
  5. basic_memory/alembic/versions/502b60eaa905_remove_required_from_entity_permalink.py +51 -0
  6. basic_memory/alembic/versions/5fe1ab1ccebe_add_projects_table.py +108 -0
  7. basic_memory/alembic/versions/647e7a75e2cd_project_constraint_fix.py +104 -0
  8. basic_memory/alembic/versions/9d9c1cb7d8f5_add_mtime_and_size_columns_to_entity_.py +49 -0
  9. basic_memory/alembic/versions/a1b2c3d4e5f6_fix_project_foreign_keys.py +49 -0
  10. basic_memory/alembic/versions/b3c3938bacdb_relation_to_name_unique_index.py +44 -0
  11. basic_memory/alembic/versions/cc7172b46608_update_search_index_schema.py +100 -0
  12. basic_memory/alembic/versions/e7e1f4367280_add_scan_watermark_tracking_to_project.py +37 -0
  13. basic_memory/api/app.py +64 -18
  14. basic_memory/api/routers/__init__.py +4 -1
  15. basic_memory/api/routers/directory_router.py +84 -0
  16. basic_memory/api/routers/importer_router.py +152 -0
  17. basic_memory/api/routers/knowledge_router.py +166 -21
  18. basic_memory/api/routers/management_router.py +80 -0
  19. basic_memory/api/routers/memory_router.py +9 -64
  20. basic_memory/api/routers/project_router.py +406 -0
  21. basic_memory/api/routers/prompt_router.py +260 -0
  22. basic_memory/api/routers/resource_router.py +119 -4
  23. basic_memory/api/routers/search_router.py +5 -5
  24. basic_memory/api/routers/utils.py +130 -0
  25. basic_memory/api/template_loader.py +292 -0
  26. basic_memory/cli/app.py +43 -9
  27. basic_memory/cli/auth.py +277 -0
  28. basic_memory/cli/commands/__init__.py +13 -2
  29. basic_memory/cli/commands/cloud/__init__.py +6 -0
  30. basic_memory/cli/commands/cloud/api_client.py +112 -0
  31. basic_memory/cli/commands/cloud/bisync_commands.py +110 -0
  32. basic_memory/cli/commands/cloud/cloud_utils.py +101 -0
  33. basic_memory/cli/commands/cloud/core_commands.py +195 -0
  34. basic_memory/cli/commands/cloud/rclone_commands.py +301 -0
  35. basic_memory/cli/commands/cloud/rclone_config.py +110 -0
  36. basic_memory/cli/commands/cloud/rclone_installer.py +249 -0
  37. basic_memory/cli/commands/cloud/upload.py +233 -0
  38. basic_memory/cli/commands/cloud/upload_command.py +124 -0
  39. basic_memory/cli/commands/command_utils.py +51 -0
  40. basic_memory/cli/commands/db.py +28 -12
  41. basic_memory/cli/commands/import_chatgpt.py +40 -220
  42. basic_memory/cli/commands/import_claude_conversations.py +41 -168
  43. basic_memory/cli/commands/import_claude_projects.py +46 -157
  44. basic_memory/cli/commands/import_memory_json.py +48 -108
  45. basic_memory/cli/commands/mcp.py +84 -10
  46. basic_memory/cli/commands/project.py +876 -0
  47. basic_memory/cli/commands/status.py +50 -33
  48. basic_memory/cli/commands/tool.py +341 -0
  49. basic_memory/cli/main.py +8 -7
  50. basic_memory/config.py +477 -23
  51. basic_memory/db.py +168 -17
  52. basic_memory/deps.py +251 -25
  53. basic_memory/file_utils.py +113 -58
  54. basic_memory/ignore_utils.py +297 -0
  55. basic_memory/importers/__init__.py +27 -0
  56. basic_memory/importers/base.py +79 -0
  57. basic_memory/importers/chatgpt_importer.py +232 -0
  58. basic_memory/importers/claude_conversations_importer.py +177 -0
  59. basic_memory/importers/claude_projects_importer.py +148 -0
  60. basic_memory/importers/memory_json_importer.py +108 -0
  61. basic_memory/importers/utils.py +58 -0
  62. basic_memory/markdown/entity_parser.py +143 -23
  63. basic_memory/markdown/markdown_processor.py +3 -3
  64. basic_memory/markdown/plugins.py +39 -21
  65. basic_memory/markdown/schemas.py +1 -1
  66. basic_memory/markdown/utils.py +28 -13
  67. basic_memory/mcp/async_client.py +134 -4
  68. basic_memory/mcp/project_context.py +141 -0
  69. basic_memory/mcp/prompts/__init__.py +19 -0
  70. basic_memory/mcp/prompts/ai_assistant_guide.py +70 -0
  71. basic_memory/mcp/prompts/continue_conversation.py +62 -0
  72. basic_memory/mcp/prompts/recent_activity.py +188 -0
  73. basic_memory/mcp/prompts/search.py +57 -0
  74. basic_memory/mcp/prompts/utils.py +162 -0
  75. basic_memory/mcp/resources/ai_assistant_guide.md +283 -0
  76. basic_memory/mcp/resources/project_info.py +71 -0
  77. basic_memory/mcp/server.py +7 -13
  78. basic_memory/mcp/tools/__init__.py +33 -21
  79. basic_memory/mcp/tools/build_context.py +120 -0
  80. basic_memory/mcp/tools/canvas.py +130 -0
  81. basic_memory/mcp/tools/chatgpt_tools.py +187 -0
  82. basic_memory/mcp/tools/delete_note.py +225 -0
  83. basic_memory/mcp/tools/edit_note.py +320 -0
  84. basic_memory/mcp/tools/list_directory.py +167 -0
  85. basic_memory/mcp/tools/move_note.py +545 -0
  86. basic_memory/mcp/tools/project_management.py +200 -0
  87. basic_memory/mcp/tools/read_content.py +271 -0
  88. basic_memory/mcp/tools/read_note.py +255 -0
  89. basic_memory/mcp/tools/recent_activity.py +534 -0
  90. basic_memory/mcp/tools/search.py +369 -23
  91. basic_memory/mcp/tools/utils.py +374 -16
  92. basic_memory/mcp/tools/view_note.py +77 -0
  93. basic_memory/mcp/tools/write_note.py +207 -0
  94. basic_memory/models/__init__.py +3 -2
  95. basic_memory/models/knowledge.py +67 -15
  96. basic_memory/models/project.py +87 -0
  97. basic_memory/models/search.py +10 -6
  98. basic_memory/repository/__init__.py +2 -0
  99. basic_memory/repository/entity_repository.py +229 -7
  100. basic_memory/repository/observation_repository.py +35 -3
  101. basic_memory/repository/project_info_repository.py +10 -0
  102. basic_memory/repository/project_repository.py +103 -0
  103. basic_memory/repository/relation_repository.py +21 -2
  104. basic_memory/repository/repository.py +147 -29
  105. basic_memory/repository/search_repository.py +411 -62
  106. basic_memory/schemas/__init__.py +22 -9
  107. basic_memory/schemas/base.py +97 -8
  108. basic_memory/schemas/cloud.py +50 -0
  109. basic_memory/schemas/directory.py +30 -0
  110. basic_memory/schemas/importer.py +35 -0
  111. basic_memory/schemas/memory.py +187 -25
  112. basic_memory/schemas/project_info.py +211 -0
  113. basic_memory/schemas/prompt.py +90 -0
  114. basic_memory/schemas/request.py +56 -2
  115. basic_memory/schemas/response.py +1 -1
  116. basic_memory/schemas/search.py +31 -35
  117. basic_memory/schemas/sync_report.py +72 -0
  118. basic_memory/services/__init__.py +2 -1
  119. basic_memory/services/context_service.py +241 -104
  120. basic_memory/services/directory_service.py +295 -0
  121. basic_memory/services/entity_service.py +590 -60
  122. basic_memory/services/exceptions.py +21 -0
  123. basic_memory/services/file_service.py +284 -30
  124. basic_memory/services/initialization.py +191 -0
  125. basic_memory/services/link_resolver.py +49 -56
  126. basic_memory/services/project_service.py +863 -0
  127. basic_memory/services/search_service.py +168 -32
  128. basic_memory/sync/__init__.py +3 -2
  129. basic_memory/sync/background_sync.py +26 -0
  130. basic_memory/sync/sync_service.py +1180 -109
  131. basic_memory/sync/watch_service.py +412 -135
  132. basic_memory/templates/prompts/continue_conversation.hbs +110 -0
  133. basic_memory/templates/prompts/search.hbs +101 -0
  134. basic_memory/utils.py +383 -51
  135. basic_memory-0.16.1.dist-info/METADATA +493 -0
  136. basic_memory-0.16.1.dist-info/RECORD +148 -0
  137. {basic_memory-0.7.0.dist-info → basic_memory-0.16.1.dist-info}/entry_points.txt +1 -0
  138. basic_memory/alembic/README +0 -1
  139. basic_memory/cli/commands/sync.py +0 -206
  140. basic_memory/cli/commands/tools.py +0 -157
  141. basic_memory/mcp/tools/knowledge.py +0 -68
  142. basic_memory/mcp/tools/memory.py +0 -170
  143. basic_memory/mcp/tools/notes.py +0 -202
  144. basic_memory/schemas/discovery.py +0 -28
  145. basic_memory/sync/file_change_scanner.py +0 -158
  146. basic_memory/sync/utils.py +0 -31
  147. basic_memory-0.7.0.dist-info/METADATA +0 -378
  148. basic_memory-0.7.0.dist-info/RECORD +0 -82
  149. {basic_memory-0.7.0.dist-info → basic_memory-0.16.1.dist-info}/WHEEL +0 -0
  150. {basic_memory-0.7.0.dist-info → basic_memory-0.16.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,38 +1,384 @@
1
1
  """Search tools for Basic Memory MCP server."""
2
2
 
3
- import logfire
3
+ from textwrap import dedent
4
+ from typing import List, Optional
5
+
4
6
  from loguru import logger
7
+ from fastmcp import Context
5
8
 
9
+ from basic_memory.mcp.async_client import get_client
10
+ from basic_memory.mcp.project_context import get_active_project
6
11
  from basic_memory.mcp.server import mcp
7
12
  from basic_memory.mcp.tools.utils import call_post
8
- from basic_memory.schemas.search import SearchQuery, SearchResponse
9
- from basic_memory.mcp.async_client import client
13
+ from basic_memory.schemas.search import SearchItemType, SearchQuery, SearchResponse
14
+
15
+
16
+ def _format_search_error_response(
17
+ project: str, error_message: str, query: str, search_type: str = "text"
18
+ ) -> str:
19
+ """Format helpful error responses for search failures that guide users to successful searches."""
20
+
21
+ # FTS5 syntax errors
22
+ if "syntax error" in error_message.lower() or "fts5" in error_message.lower():
23
+ clean_query = (
24
+ query.replace('"', "")
25
+ .replace("(", "")
26
+ .replace(")", "")
27
+ .replace("+", "")
28
+ .replace("*", "")
29
+ )
30
+ return dedent(f"""
31
+ # Search Failed - Invalid Syntax
32
+
33
+ The search query '{query}' contains invalid syntax that the search engine cannot process.
34
+
35
+ ## Common syntax issues:
36
+ 1. **Special characters**: Characters like `+`, `*`, `"`, `(`, `)` have special meaning in search
37
+ 2. **Unmatched quotes**: Make sure quotes are properly paired
38
+ 3. **Invalid operators**: Check AND, OR, NOT operators are used correctly
39
+
40
+ ## How to fix:
41
+ 1. **Simplify your search**: Try using simple words instead: `{clean_query}`
42
+ 2. **Remove special characters**: Use alphanumeric characters and spaces
43
+ 3. **Use basic boolean operators**: `word1 AND word2`, `word1 OR word2`, `word1 NOT word2`
44
+
45
+ ## Examples of valid searches:
46
+ - Simple text: `project planning`
47
+ - Boolean AND: `project AND planning`
48
+ - Boolean OR: `meeting OR discussion`
49
+ - Boolean NOT: `project NOT archived`
50
+ - Grouped: `(project OR planning) AND notes`
51
+ - Exact phrases: `"weekly standup meeting"`
52
+ - Content-specific: `tag:example` or `category:observation`
53
+
54
+ ## Try again with:
55
+ ```
56
+ search_notes("{project}","{clean_query}")
57
+ ```
58
+
59
+ ## Alternative search strategies:
60
+ - Break into simpler terms: `search_notes("{project}", "{" ".join(clean_query.split()[:2])}")`
61
+ - Try different search types: `search_notes("{project}","{clean_query}", search_type="title")`
62
+ - Use filtering: `search_notes("{project}","{clean_query}", types=["entity"])`
63
+ """).strip()
64
+
65
+ # Project not found errors (check before general "not found")
66
+ if "project not found" in error_message.lower():
67
+ return dedent(f"""
68
+ # Search Failed - Project Not Found
69
+
70
+ The current project is not accessible or doesn't exist: {error_message}
71
+
72
+ ## How to resolve:
73
+ 1. **Check available projects**: `list_projects()`
74
+ 3. **Verify project setup**: Ensure your project is properly configured
75
+
76
+ ## Current session info:
77
+ - See available projects: `list_projects()`
78
+ """).strip()
79
+
80
+ # No results found
81
+ if "no results" in error_message.lower() or "not found" in error_message.lower():
82
+ simplified_query = (
83
+ " ".join(query.split()[:2])
84
+ if len(query.split()) > 2
85
+ else query.split()[0]
86
+ if query.split()
87
+ else "notes"
88
+ )
89
+ return dedent(f"""
90
+ # Search Complete - No Results Found
91
+
92
+ No content found matching '{query}' in the current project.
93
+
94
+ ## Search strategy suggestions:
95
+ 1. **Broaden your search**: Try fewer or more general terms
96
+ - Instead of: `{query}`
97
+ - Try: `{simplified_query}`
98
+
99
+ 2. **Check spelling and try variations**:
100
+ - Verify terms are spelled correctly
101
+ - Try synonyms or related terms
102
+
103
+ 3. **Use different search approaches**:
104
+ - **Text search**: `search_notes("{project}","{query}", search_type="text")` (searches full content)
105
+ - **Title search**: `search_notes("{project}","{query}", search_type="title")` (searches only titles)
106
+ - **Permalink search**: `search_notes("{project}","{query}", search_type="permalink")` (searches file paths)
107
+
108
+ 4. **Try boolean operators for broader results**:
109
+ - OR search: `search_notes("{project}","{" OR ".join(query.split()[:3])}")`
110
+ - Remove restrictive terms: Focus on the most important keywords
111
+
112
+ 5. **Use filtering to narrow scope**:
113
+ - By content type: `search_notes("{project}","{query}", types=["entity"])`
114
+ - By recent content: `search_notes("{project}","{query}", after_date="1 week")`
115
+ - By entity type: `search_notes("{project}","{query}", entity_types=["observation"])`
116
+
117
+ 6. **Try advanced search patterns**:
118
+ - Tag search: `search_notes("{project}","tag:your-tag")`
119
+ - Category search: `search_notes("{project}","category:observation")`
120
+ - Pattern matching: `search_notes("{project}","*{query}*", search_type="permalink")`
121
+
122
+ ## Explore what content exists:
123
+ - **Recent activity**: `recent_activity(timeframe="7d")` - See what's been updated recently
124
+ - **List directories**: `list_directory("{project}","/")` - Browse all content
125
+ - **Browse by folder**: `list_directory("{project}","/notes")` or `list_directory("/docs")`
126
+ """).strip()
127
+
128
+ # Server/API errors
129
+ if "server error" in error_message.lower() or "internal" in error_message.lower():
130
+ return dedent(f"""
131
+ # Search Failed - Server Error
132
+
133
+ The search service encountered an error while processing '{query}': {error_message}
134
+
135
+ ## Immediate steps:
136
+ 1. **Try again**: The error might be temporary
137
+ 2. **Simplify the query**: Use simpler search terms
138
+ 3. **Check project status**: Ensure your project is properly synced
139
+
140
+ ## Alternative approaches:
141
+ - Browse files directly: `list_directory("{project}","/")`
142
+ - Check recent activity: `recent_activity(timeframe="7d")`
143
+ - Try a different search type: `search_notes("{project}","{query}", search_type="title")`
144
+
145
+ ## If the problem persists:
146
+ The search index might need to be rebuilt. Send a message to support@basicmachines.co or check the project sync status.
147
+ """).strip()
148
+
149
+ # Permission/access errors
150
+ if (
151
+ "permission" in error_message.lower()
152
+ or "access" in error_message.lower()
153
+ or "forbidden" in error_message.lower()
154
+ ):
155
+ return f"""# Search Failed - Access Error
156
+
157
+ You don't have permission to search in the current project: {error_message}
158
+
159
+ ## How to resolve:
160
+ 1. **Check your project access**: Verify you have read permissions for this project
161
+ 2. **Switch projects**: Try searching in a different project you have access to
162
+ 3. **Check authentication**: You might need to re-authenticate
163
+
164
+ ## Alternative actions:
165
+ - List available projects: `list_projects()`"""
166
+
167
+ # Generic fallback
168
+ return f"""# Search Failed
169
+
170
+ Error searching for '{query}': {error_message}
171
+
172
+ ## Troubleshooting steps:
173
+ 1. **Simplify your query**: Try basic words without special characters
174
+ 2. **Check search syntax**: Ensure boolean operators are correctly formatted
175
+ 3. **Verify project access**: Make sure you can access the current project
176
+ 4. **Test with simple search**: Try `search_notes("test")` to verify search is working
177
+
178
+ ## Alternative search approaches:
179
+ - **Different search types**:
180
+ - Title only: `search_notes("{project}","{query}", search_type="title")`
181
+ - Permalink patterns: `search_notes("{project}","{query}*", search_type="permalink")`
182
+ - **With filters**: `search_notes("{project}","{query}", types=["entity"])`
183
+ - **Recent content**: `search_notes("{project}","{query}", after_date="1 week")`
184
+ - **Boolean variations**: `search_notes("{project}","{" OR ".join(query.split()[:2])}")`
185
+
186
+ ## Explore your content:
187
+ - **Browse files**: `list_directory("{project}","/")` - See all available content
188
+ - **Recent activity**: `recent_activity(timeframe="7d")` - Check what's been updated
189
+ - **All projects**: `list_projects()`
190
+
191
+ ## Search syntax reference:
192
+ - **Basic**: `keyword` or `multiple words`
193
+ - **Boolean**: `term1 AND term2`, `term1 OR term2`, `term1 NOT term2`
194
+ - **Phrases**: `"exact phrase"`
195
+ - **Grouping**: `(term1 OR term2) AND term3`
196
+ - **Patterns**: `tag:example`, `category:observation`"""
10
197
 
11
198
 
12
199
  @mcp.tool(
13
- description="Search across all content in basic-memory, including documents and entities",
200
+ description="Search across all content in the knowledge base with advanced syntax support.",
14
201
  )
15
- async def search(query: SearchQuery, page: int = 1, page_size: int = 10) -> SearchResponse:
16
- """Search across all content in basic-memory.
202
+ async def search_notes(
203
+ query: str,
204
+ project: Optional[str] = None,
205
+ page: int = 1,
206
+ page_size: int = 10,
207
+ search_type: str = "text",
208
+ types: List[str] = [],
209
+ entity_types: List[str] = [],
210
+ after_date: Optional[str] = None,
211
+ context: Context | None = None,
212
+ ) -> SearchResponse | str:
213
+ """Search across all content in the knowledge base with comprehensive syntax support.
214
+
215
+ This tool searches the knowledge base using full-text search, pattern matching,
216
+ or exact permalink lookup. It supports filtering by content type, entity type,
217
+ and date, with advanced boolean and phrase search capabilities.
218
+
219
+ Project Resolution:
220
+ Server resolves projects in this order: Single Project Mode → project parameter → default project.
221
+ If project unknown, use list_memory_projects() or recent_activity() first.
222
+
223
+ ## Search Syntax Examples
224
+
225
+ ### Basic Searches
226
+ - `search_notes("my-project", "keyword")` - Find any content containing "keyword"
227
+ - `search_notes("work-docs", "'exact phrase'")` - Search for exact phrase match
228
+
229
+ ### Advanced Boolean Searches
230
+ - `search_notes("my-project", "term1 term2")` - Find content with both terms (implicit AND)
231
+ - `search_notes("my-project", "term1 AND term2")` - Explicit AND search (both terms required)
232
+ - `search_notes("my-project", "term1 OR term2")` - Either term can be present
233
+ - `search_notes("my-project", "term1 NOT term2")` - Include term1 but exclude term2
234
+ - `search_notes("my-project", "(project OR planning) AND notes")` - Grouped boolean logic
235
+
236
+ ### Content-Specific Searches
237
+ - `search_notes("research", "tag:example")` - Search within specific tags (if supported by content)
238
+ - `search_notes("work-project", "category:observation")` - Filter by observation categories
239
+ - `search_notes("team-docs", "author:username")` - Find content by author (if metadata available)
240
+
241
+ ### Search Type Examples
242
+ - `search_notes("my-project", "Meeting", search_type="title")` - Search only in titles
243
+ - `search_notes("work-docs", "docs/meeting-*", search_type="permalink")` - Pattern match permalinks
244
+ - `search_notes("research", "keyword", search_type="text")` - Full-text search (default)
245
+
246
+ ### Filtering Options
247
+ - `search_notes("my-project", "query", types=["entity"])` - Search only entities
248
+ - `search_notes("work-docs", "query", types=["note", "person"])` - Multiple content types
249
+ - `search_notes("research", "query", entity_types=["observation"])` - Filter by entity type
250
+ - `search_notes("team-docs", "query", after_date="2024-01-01")` - Recent content only
251
+ - `search_notes("my-project", "query", after_date="1 week")` - Relative date filtering
252
+
253
+ ### Advanced Pattern Examples
254
+ - `search_notes("work-project", "project AND (meeting OR discussion)")` - Complex boolean logic
255
+ - `search_notes("research", "\"exact phrase\" AND keyword")` - Combine phrase and keyword search
256
+ - `search_notes("dev-notes", "bug NOT fixed")` - Exclude resolved issues
257
+ - `search_notes("archive", "docs/2024-*", search_type="permalink")` - Year-based permalink search
17
258
 
18
259
  Args:
19
- query: SearchQuery object with search parameters including:
20
- - text: Search text (required)
21
- - types: Optional list of content types to search ("document" or "entity")
22
- - entity_types: Optional list of entity types to filter by
23
- - after_date: Optional date filter for recent content
24
- page: the page number of results to return (default 1)
25
- page_size: the number of results to return per page (default 10)
260
+ query: The search query string (supports boolean operators, phrases, patterns)
261
+ project: Project name to search in. Optional - server will resolve using hierarchy.
262
+ If unknown, use list_memory_projects() to discover available projects.
263
+ page: The page number of results to return (default 1)
264
+ page_size: The number of results to return per page (default 10)
265
+ search_type: Type of search to perform, one of: "text", "title", "permalink" (default: "text")
266
+ types: Optional list of note types to search (e.g., ["note", "person"])
267
+ entity_types: Optional list of entity types to filter by (e.g., ["entity", "observation"])
268
+ after_date: Optional date filter for recent content (e.g., "1 week", "2d", "2024-01-01")
269
+ context: Optional FastMCP context for performance caching.
26
270
 
27
271
  Returns:
28
- SearchResponse with search results and metadata
272
+ SearchResponse with results and pagination info, or helpful error guidance if search fails
273
+
274
+ Examples:
275
+ # Basic text search
276
+ results = await search_notes("project planning")
277
+
278
+ # Boolean AND search (both terms must be present)
279
+ results = await search_notes("project AND planning")
280
+
281
+ # Boolean OR search (either term can be present)
282
+ results = await search_notes("project OR meeting")
283
+
284
+ # Boolean NOT search (exclude terms)
285
+ results = await search_notes("project NOT meeting")
286
+
287
+ # Boolean search with grouping
288
+ results = await search_notes("(project OR planning) AND notes")
289
+
290
+ # Exact phrase search
291
+ results = await search_notes("\"weekly standup meeting\"")
292
+
293
+ # Search with type filter
294
+ results = await search_notes(
295
+ "meeting notes",
296
+ types=["entity"],
297
+ )
298
+
299
+ # Search with entity type filter
300
+ results = await search_notes(
301
+ "meeting notes",
302
+ entity_types=["observation"],
303
+ )
304
+
305
+ # Search for recent content
306
+ results = await search_notes(
307
+ "bug report",
308
+ after_date="1 week"
309
+ )
310
+
311
+ # Pattern matching on permalinks
312
+ results = await search_notes(
313
+ "docs/meeting-*",
314
+ search_type="permalink"
315
+ )
316
+
317
+ # Title-only search
318
+ results = await search_notes(
319
+ "Machine Learning",
320
+ search_type="title"
321
+ )
322
+
323
+ # Complex search with multiple filters
324
+ results = await search_notes(
325
+ "(bug OR issue) AND NOT resolved",
326
+ types=["entity"],
327
+ after_date="2024-01-01"
328
+ )
329
+
330
+ # Explicit project specification
331
+ results = await search_notes("project planning", project="my-project")
29
332
  """
30
- with logfire.span("Searching for {query}", query=query): # pyright: ignore [reportGeneralTypeIssues]
31
- logger.info(f"Searching for {query}")
32
- response = await call_post(
33
- client,
34
- "/search/",
35
- json=query.model_dump(),
36
- params={"page": page, "page_size": page_size},
37
- )
38
- return SearchResponse.model_validate(response.json())
333
+ # Create a SearchQuery object based on the parameters
334
+ search_query = SearchQuery()
335
+
336
+ # Set the appropriate search field based on search_type
337
+ if search_type == "text":
338
+ search_query.text = query
339
+ elif search_type == "title":
340
+ search_query.title = query
341
+ elif search_type == "permalink" and "*" in query:
342
+ search_query.permalink_match = query
343
+ elif search_type == "permalink":
344
+ search_query.permalink = query
345
+ else:
346
+ search_query.text = query # Default to text search
347
+
348
+ # Add optional filters if provided (empty lists are treated as no filter)
349
+ if entity_types:
350
+ search_query.entity_types = [SearchItemType(t) for t in entity_types]
351
+ if types:
352
+ search_query.types = types
353
+ if after_date:
354
+ search_query.after_date = after_date
355
+
356
+ async with get_client() as client:
357
+ active_project = await get_active_project(client, project, context)
358
+ project_url = active_project.project_url
359
+
360
+ logger.info(f"Searching for {search_query} in project {active_project.name}")
361
+
362
+ try:
363
+ response = await call_post(
364
+ client,
365
+ f"{project_url}/search/",
366
+ json=search_query.model_dump(),
367
+ params={"page": page, "page_size": page_size},
368
+ )
369
+ result = SearchResponse.model_validate(response.json())
370
+
371
+ # Check if we got no results and provide helpful guidance
372
+ if not result.results:
373
+ logger.info(
374
+ f"Search returned no results for query: {query} in project {active_project.name}"
375
+ )
376
+ # Don't treat this as an error, but the user might want guidance
377
+ # We return the empty result as normal - the user can decide if they need help
378
+
379
+ return result
380
+
381
+ except Exception as e:
382
+ logger.error(f"Search failed for query '{query}': {e}, project: {active_project.name}")
383
+ # Return formatted error message as string for better user experience
384
+ return _format_search_error_response(active_project.name, str(e), query, search_type)