basic-memory 0.14.4__py3-none-any.whl → 0.15.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.
- basic_memory/__init__.py +1 -1
- basic_memory/alembic/versions/a1b2c3d4e5f6_fix_project_foreign_keys.py +5 -9
- basic_memory/api/app.py +10 -4
- basic_memory/api/routers/directory_router.py +23 -2
- basic_memory/api/routers/knowledge_router.py +25 -8
- basic_memory/api/routers/project_router.py +100 -4
- basic_memory/cli/app.py +9 -28
- basic_memory/cli/auth.py +277 -0
- basic_memory/cli/commands/cloud/__init__.py +5 -0
- basic_memory/cli/commands/cloud/api_client.py +112 -0
- basic_memory/cli/commands/cloud/bisync_commands.py +818 -0
- basic_memory/cli/commands/cloud/core_commands.py +288 -0
- basic_memory/cli/commands/cloud/mount_commands.py +295 -0
- basic_memory/cli/commands/cloud/rclone_config.py +288 -0
- basic_memory/cli/commands/cloud/rclone_installer.py +198 -0
- basic_memory/cli/commands/command_utils.py +43 -0
- basic_memory/cli/commands/import_memory_json.py +0 -4
- basic_memory/cli/commands/mcp.py +77 -60
- basic_memory/cli/commands/project.py +154 -152
- basic_memory/cli/commands/status.py +25 -22
- basic_memory/cli/commands/sync.py +45 -228
- basic_memory/cli/commands/tool.py +87 -16
- basic_memory/cli/main.py +1 -0
- basic_memory/config.py +131 -21
- basic_memory/db.py +104 -3
- basic_memory/deps.py +27 -8
- basic_memory/file_utils.py +37 -13
- basic_memory/ignore_utils.py +295 -0
- basic_memory/markdown/plugins.py +9 -7
- basic_memory/mcp/async_client.py +124 -14
- basic_memory/mcp/project_context.py +141 -0
- basic_memory/mcp/prompts/ai_assistant_guide.py +49 -4
- basic_memory/mcp/prompts/continue_conversation.py +17 -16
- basic_memory/mcp/prompts/recent_activity.py +116 -32
- basic_memory/mcp/prompts/search.py +13 -12
- basic_memory/mcp/prompts/utils.py +11 -4
- basic_memory/mcp/resources/ai_assistant_guide.md +211 -341
- basic_memory/mcp/resources/project_info.py +27 -11
- basic_memory/mcp/server.py +0 -37
- basic_memory/mcp/tools/__init__.py +5 -6
- basic_memory/mcp/tools/build_context.py +67 -56
- basic_memory/mcp/tools/canvas.py +38 -26
- basic_memory/mcp/tools/chatgpt_tools.py +187 -0
- basic_memory/mcp/tools/delete_note.py +81 -47
- basic_memory/mcp/tools/edit_note.py +155 -138
- basic_memory/mcp/tools/list_directory.py +112 -99
- basic_memory/mcp/tools/move_note.py +181 -101
- basic_memory/mcp/tools/project_management.py +113 -277
- basic_memory/mcp/tools/read_content.py +91 -74
- basic_memory/mcp/tools/read_note.py +152 -115
- basic_memory/mcp/tools/recent_activity.py +471 -68
- basic_memory/mcp/tools/search.py +105 -92
- basic_memory/mcp/tools/sync_status.py +136 -130
- basic_memory/mcp/tools/utils.py +4 -0
- basic_memory/mcp/tools/view_note.py +44 -33
- basic_memory/mcp/tools/write_note.py +151 -90
- basic_memory/models/knowledge.py +12 -6
- basic_memory/models/project.py +6 -2
- basic_memory/repository/entity_repository.py +89 -82
- basic_memory/repository/relation_repository.py +13 -0
- basic_memory/repository/repository.py +18 -5
- basic_memory/repository/search_repository.py +46 -2
- basic_memory/schemas/__init__.py +6 -0
- basic_memory/schemas/base.py +39 -11
- basic_memory/schemas/cloud.py +46 -0
- basic_memory/schemas/memory.py +90 -21
- basic_memory/schemas/project_info.py +9 -10
- basic_memory/schemas/sync_report.py +48 -0
- basic_memory/services/context_service.py +25 -11
- basic_memory/services/directory_service.py +124 -3
- basic_memory/services/entity_service.py +100 -48
- basic_memory/services/initialization.py +30 -11
- basic_memory/services/project_service.py +101 -24
- basic_memory/services/search_service.py +16 -8
- basic_memory/sync/sync_service.py +173 -34
- basic_memory/sync/watch_service.py +101 -40
- basic_memory/utils.py +14 -4
- {basic_memory-0.14.4.dist-info → basic_memory-0.15.1.dist-info}/METADATA +57 -9
- basic_memory-0.15.1.dist-info/RECORD +146 -0
- basic_memory/mcp/project_session.py +0 -120
- basic_memory-0.14.4.dist-info/RECORD +0 -133
- {basic_memory-0.14.4.dist-info → basic_memory-0.15.1.dist-info}/WHEEL +0 -0
- {basic_memory-0.14.4.dist-info → basic_memory-0.15.1.dist-info}/entry_points.txt +0 -0
- {basic_memory-0.14.4.dist-info → basic_memory-0.15.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,242 +1,59 @@
|
|
|
1
1
|
"""Command module for basic-memory sync operations."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
-
from
|
|
5
|
-
from dataclasses import dataclass
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
from typing import List, Dict
|
|
4
|
+
from typing import Annotated, Optional
|
|
8
5
|
|
|
9
6
|
import typer
|
|
10
|
-
from loguru import logger
|
|
11
|
-
from rich.console import Console
|
|
12
|
-
from rich.tree import Tree
|
|
13
7
|
|
|
14
|
-
from basic_memory import db
|
|
15
8
|
from basic_memory.cli.app import app
|
|
16
|
-
from basic_memory.
|
|
17
|
-
from basic_memory.
|
|
18
|
-
from basic_memory.markdown.markdown_processor import MarkdownProcessor
|
|
19
|
-
from basic_memory.models import Project
|
|
20
|
-
from basic_memory.repository import (
|
|
21
|
-
EntityRepository,
|
|
22
|
-
ObservationRepository,
|
|
23
|
-
RelationRepository,
|
|
24
|
-
ProjectRepository,
|
|
25
|
-
)
|
|
26
|
-
from basic_memory.repository.search_repository import SearchRepository
|
|
27
|
-
from basic_memory.services import EntityService, FileService
|
|
28
|
-
from basic_memory.services.link_resolver import LinkResolver
|
|
29
|
-
from basic_memory.services.search_service import SearchService
|
|
30
|
-
from basic_memory.sync import SyncService
|
|
31
|
-
from basic_memory.sync.sync_service import SyncReport
|
|
32
|
-
|
|
33
|
-
console = Console()
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
@dataclass
|
|
37
|
-
class ValidationIssue:
|
|
38
|
-
file_path: str
|
|
39
|
-
error: str
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
async def get_sync_service(project: Project) -> SyncService: # pragma: no cover
|
|
43
|
-
"""Get sync service instance with all dependencies."""
|
|
44
|
-
|
|
45
|
-
app_config = ConfigManager().config
|
|
46
|
-
_, session_maker = await db.get_or_create_db(
|
|
47
|
-
db_path=app_config.database_path, db_type=db.DatabaseType.FILESYSTEM
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
project_path = Path(project.path)
|
|
51
|
-
entity_parser = EntityParser(project_path)
|
|
52
|
-
markdown_processor = MarkdownProcessor(entity_parser)
|
|
53
|
-
file_service = FileService(project_path, markdown_processor)
|
|
54
|
-
|
|
55
|
-
# Initialize repositories
|
|
56
|
-
entity_repository = EntityRepository(session_maker, project_id=project.id)
|
|
57
|
-
observation_repository = ObservationRepository(session_maker, project_id=project.id)
|
|
58
|
-
relation_repository = RelationRepository(session_maker, project_id=project.id)
|
|
59
|
-
search_repository = SearchRepository(session_maker, project_id=project.id)
|
|
60
|
-
|
|
61
|
-
# Initialize services
|
|
62
|
-
search_service = SearchService(search_repository, entity_repository, file_service)
|
|
63
|
-
link_resolver = LinkResolver(entity_repository, search_service)
|
|
64
|
-
|
|
65
|
-
# Initialize services
|
|
66
|
-
entity_service = EntityService(
|
|
67
|
-
entity_parser,
|
|
68
|
-
entity_repository,
|
|
69
|
-
observation_repository,
|
|
70
|
-
relation_repository,
|
|
71
|
-
file_service,
|
|
72
|
-
link_resolver,
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
# Create sync service
|
|
76
|
-
sync_service = SyncService(
|
|
77
|
-
app_config=app_config,
|
|
78
|
-
entity_service=entity_service,
|
|
79
|
-
entity_parser=entity_parser,
|
|
80
|
-
entity_repository=entity_repository,
|
|
81
|
-
relation_repository=relation_repository,
|
|
82
|
-
search_service=search_service,
|
|
83
|
-
file_service=file_service,
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
return sync_service
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def group_issues_by_directory(issues: List[ValidationIssue]) -> Dict[str, List[ValidationIssue]]:
|
|
90
|
-
"""Group validation issues by directory."""
|
|
91
|
-
grouped = defaultdict(list)
|
|
92
|
-
for issue in issues:
|
|
93
|
-
dir_name = Path(issue.file_path).parent.name
|
|
94
|
-
grouped[dir_name].append(issue)
|
|
95
|
-
return dict(grouped)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def display_sync_summary(knowledge: SyncReport):
|
|
99
|
-
"""Display a one-line summary of sync changes."""
|
|
100
|
-
config = get_project_config()
|
|
101
|
-
total_changes = knowledge.total
|
|
102
|
-
project_name = config.project
|
|
103
|
-
|
|
104
|
-
if total_changes == 0:
|
|
105
|
-
console.print(f"[green]Project '{project_name}': Everything up to date[/green]")
|
|
106
|
-
return
|
|
107
|
-
|
|
108
|
-
# Format as: "Synced X files (A new, B modified, C moved, D deleted)"
|
|
109
|
-
changes = []
|
|
110
|
-
new_count = len(knowledge.new)
|
|
111
|
-
mod_count = len(knowledge.modified)
|
|
112
|
-
move_count = len(knowledge.moves)
|
|
113
|
-
del_count = len(knowledge.deleted)
|
|
114
|
-
|
|
115
|
-
if new_count:
|
|
116
|
-
changes.append(f"[green]{new_count} new[/green]")
|
|
117
|
-
if mod_count:
|
|
118
|
-
changes.append(f"[yellow]{mod_count} modified[/yellow]")
|
|
119
|
-
if move_count:
|
|
120
|
-
changes.append(f"[blue]{move_count} moved[/blue]")
|
|
121
|
-
if del_count:
|
|
122
|
-
changes.append(f"[red]{del_count} deleted[/red]")
|
|
123
|
-
|
|
124
|
-
console.print(f"Project '{project_name}': Synced {total_changes} files ({', '.join(changes)})")
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
def display_detailed_sync_results(knowledge: SyncReport):
|
|
128
|
-
"""Display detailed sync results with trees."""
|
|
129
|
-
config = get_project_config()
|
|
130
|
-
project_name = config.project
|
|
131
|
-
|
|
132
|
-
if knowledge.total == 0:
|
|
133
|
-
console.print(f"\n[green]Project '{project_name}': Everything up to date[/green]")
|
|
134
|
-
return
|
|
135
|
-
|
|
136
|
-
console.print(f"\n[bold]Sync Results for Project '{project_name}'[/bold]")
|
|
137
|
-
|
|
138
|
-
if knowledge.total > 0:
|
|
139
|
-
knowledge_tree = Tree("[bold]Knowledge Files[/bold]")
|
|
140
|
-
if knowledge.new:
|
|
141
|
-
created = knowledge_tree.add("[green]Created[/green]")
|
|
142
|
-
for path in sorted(knowledge.new):
|
|
143
|
-
checksum = knowledge.checksums.get(path, "")
|
|
144
|
-
created.add(f"[green]{path}[/green] ({checksum[:8]})")
|
|
145
|
-
if knowledge.modified:
|
|
146
|
-
modified = knowledge_tree.add("[yellow]Modified[/yellow]")
|
|
147
|
-
for path in sorted(knowledge.modified):
|
|
148
|
-
checksum = knowledge.checksums.get(path, "")
|
|
149
|
-
modified.add(f"[yellow]{path}[/yellow] ({checksum[:8]})")
|
|
150
|
-
if knowledge.moves:
|
|
151
|
-
moved = knowledge_tree.add("[blue]Moved[/blue]")
|
|
152
|
-
for old_path, new_path in sorted(knowledge.moves.items()):
|
|
153
|
-
checksum = knowledge.checksums.get(new_path, "")
|
|
154
|
-
moved.add(f"[blue]{old_path}[/blue] → [blue]{new_path}[/blue] ({checksum[:8]})")
|
|
155
|
-
if knowledge.deleted:
|
|
156
|
-
deleted = knowledge_tree.add("[red]Deleted[/red]")
|
|
157
|
-
for path in sorted(knowledge.deleted):
|
|
158
|
-
deleted.add(f"[red]{path}[/red]")
|
|
159
|
-
console.print(knowledge_tree)
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
async def run_sync(verbose: bool = False):
|
|
163
|
-
"""Run sync operation."""
|
|
164
|
-
app_config = ConfigManager().config
|
|
165
|
-
config = get_project_config()
|
|
166
|
-
|
|
167
|
-
_, session_maker = await db.get_or_create_db(
|
|
168
|
-
db_path=app_config.database_path, db_type=db.DatabaseType.FILESYSTEM
|
|
169
|
-
)
|
|
170
|
-
project_repository = ProjectRepository(session_maker)
|
|
171
|
-
project = await project_repository.get_by_name(config.project)
|
|
172
|
-
if not project: # pragma: no cover
|
|
173
|
-
raise Exception(f"Project '{config.project}' not found")
|
|
174
|
-
|
|
175
|
-
import time
|
|
176
|
-
|
|
177
|
-
start_time = time.time()
|
|
178
|
-
|
|
179
|
-
logger.info(
|
|
180
|
-
"Sync command started",
|
|
181
|
-
project=config.project,
|
|
182
|
-
verbose=verbose,
|
|
183
|
-
directory=str(config.home),
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
sync_service = await get_sync_service(project)
|
|
187
|
-
|
|
188
|
-
logger.info("Running one-time sync")
|
|
189
|
-
knowledge_changes = await sync_service.sync(config.home, project_name=project.name)
|
|
190
|
-
|
|
191
|
-
# Log results
|
|
192
|
-
duration_ms = int((time.time() - start_time) * 1000)
|
|
193
|
-
logger.info(
|
|
194
|
-
"Sync command completed",
|
|
195
|
-
project=config.project,
|
|
196
|
-
total_changes=knowledge_changes.total,
|
|
197
|
-
new_files=len(knowledge_changes.new),
|
|
198
|
-
modified_files=len(knowledge_changes.modified),
|
|
199
|
-
deleted_files=len(knowledge_changes.deleted),
|
|
200
|
-
moved_files=len(knowledge_changes.moves),
|
|
201
|
-
duration_ms=duration_ms,
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
# Display results
|
|
205
|
-
if verbose:
|
|
206
|
-
display_detailed_sync_results(knowledge_changes)
|
|
207
|
-
else:
|
|
208
|
-
display_sync_summary(knowledge_changes) # pragma: no cover
|
|
9
|
+
from basic_memory.cli.commands.command_utils import run_sync
|
|
10
|
+
from basic_memory.config import ConfigManager
|
|
209
11
|
|
|
210
12
|
|
|
211
13
|
@app.command()
|
|
212
14
|
def sync(
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
"
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
15
|
+
project: Annotated[
|
|
16
|
+
Optional[str],
|
|
17
|
+
typer.Option(help="The project name."),
|
|
18
|
+
] = None,
|
|
19
|
+
watch: Annotated[
|
|
20
|
+
bool,
|
|
21
|
+
typer.Option("--watch", help="Run continuous sync (cloud mode only)"),
|
|
22
|
+
] = False,
|
|
23
|
+
interval: Annotated[
|
|
24
|
+
int,
|
|
25
|
+
typer.Option("--interval", help="Sync interval in seconds for watch mode (default: 60)"),
|
|
26
|
+
] = 60,
|
|
219
27
|
) -> None:
|
|
220
|
-
"""Sync knowledge files with the database.
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
28
|
+
"""Sync knowledge files with the database.
|
|
29
|
+
|
|
30
|
+
In local mode: Scans filesystem and updates database.
|
|
31
|
+
In cloud mode: Runs bidirectional file sync (bisync) then updates database.
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
bm sync # One-time sync
|
|
35
|
+
bm sync --watch # Continuous sync every 60s
|
|
36
|
+
bm sync --watch --interval 30 # Continuous sync every 30s
|
|
37
|
+
"""
|
|
38
|
+
config = ConfigManager().config
|
|
39
|
+
|
|
40
|
+
if config.cloud_mode_enabled:
|
|
41
|
+
# Cloud mode: run bisync which includes database sync
|
|
42
|
+
from basic_memory.cli.commands.cloud.bisync_commands import run_bisync, run_bisync_watch
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
if watch:
|
|
46
|
+
run_bisync_watch(interval_seconds=interval)
|
|
47
|
+
else:
|
|
48
|
+
run_bisync()
|
|
49
|
+
except Exception:
|
|
50
|
+
raise typer.Exit(1)
|
|
51
|
+
else:
|
|
52
|
+
# Local mode: just database sync
|
|
53
|
+
if watch:
|
|
54
|
+
typer.echo(
|
|
55
|
+
"Error: --watch is only available in cloud mode. Run 'bm cloud login' first."
|
|
239
56
|
)
|
|
240
|
-
typer.echo(f"Error during sync: {e}", err=True)
|
|
241
57
|
raise typer.Exit(1)
|
|
242
|
-
|
|
58
|
+
|
|
59
|
+
asyncio.run(run_sync(project))
|
|
@@ -9,6 +9,7 @@ from loguru import logger
|
|
|
9
9
|
from rich import print as rprint
|
|
10
10
|
|
|
11
11
|
from basic_memory.cli.app import app
|
|
12
|
+
from basic_memory.config import ConfigManager
|
|
12
13
|
|
|
13
14
|
# Import prompts
|
|
14
15
|
from basic_memory.mcp.prompts.continue_conversation import (
|
|
@@ -34,6 +35,12 @@ app.add_typer(tool_app, name="tool", help="Access to MCP tools via CLI")
|
|
|
34
35
|
def write_note(
|
|
35
36
|
title: Annotated[str, typer.Option(help="The title of the note")],
|
|
36
37
|
folder: Annotated[str, typer.Option(help="The folder to create the note in")],
|
|
38
|
+
project: Annotated[
|
|
39
|
+
Optional[str],
|
|
40
|
+
typer.Option(
|
|
41
|
+
help="The project to write to. If not provided, the default project will be used."
|
|
42
|
+
),
|
|
43
|
+
] = None,
|
|
37
44
|
content: Annotated[
|
|
38
45
|
Optional[str],
|
|
39
46
|
typer.Option(
|
|
@@ -90,7 +97,19 @@ def write_note(
|
|
|
90
97
|
typer.echo("Empty content provided. Please provide non-empty content.", err=True)
|
|
91
98
|
raise typer.Exit(1)
|
|
92
99
|
|
|
93
|
-
|
|
100
|
+
# look for the project in the config
|
|
101
|
+
config_manager = ConfigManager()
|
|
102
|
+
project_name = None
|
|
103
|
+
if project is not None:
|
|
104
|
+
project_name, _ = config_manager.get_project(project)
|
|
105
|
+
if not project_name:
|
|
106
|
+
typer.echo(f"No project found named: {project}", err=True)
|
|
107
|
+
raise typer.Exit(1)
|
|
108
|
+
|
|
109
|
+
# use the project name, or the default from the config
|
|
110
|
+
project_name = project_name or config_manager.default_project
|
|
111
|
+
|
|
112
|
+
note = asyncio.run(mcp_write_note.fn(title, content, folder, project_name, tags))
|
|
94
113
|
rprint(note)
|
|
95
114
|
except Exception as e: # pragma: no cover
|
|
96
115
|
if not isinstance(e, typer.Exit):
|
|
@@ -100,10 +119,33 @@ def write_note(
|
|
|
100
119
|
|
|
101
120
|
|
|
102
121
|
@tool_app.command()
|
|
103
|
-
def read_note(
|
|
122
|
+
def read_note(
|
|
123
|
+
identifier: str,
|
|
124
|
+
project: Annotated[
|
|
125
|
+
Optional[str],
|
|
126
|
+
typer.Option(
|
|
127
|
+
help="The project to use for the note. If not provided, the default project will be used."
|
|
128
|
+
),
|
|
129
|
+
] = None,
|
|
130
|
+
page: int = 1,
|
|
131
|
+
page_size: int = 10,
|
|
132
|
+
):
|
|
104
133
|
"""Read a markdown note from the knowledge base."""
|
|
134
|
+
|
|
135
|
+
# look for the project in the config
|
|
136
|
+
config_manager = ConfigManager()
|
|
137
|
+
project_name = None
|
|
138
|
+
if project is not None:
|
|
139
|
+
project_name, _ = config_manager.get_project(project)
|
|
140
|
+
if not project_name:
|
|
141
|
+
typer.echo(f"No project found named: {project}", err=True)
|
|
142
|
+
raise typer.Exit(1)
|
|
143
|
+
|
|
144
|
+
# use the project name, or the default from the config
|
|
145
|
+
project_name = project_name or config_manager.default_project
|
|
146
|
+
|
|
105
147
|
try:
|
|
106
|
-
note = asyncio.run(mcp_read_note.fn(identifier, page, page_size))
|
|
148
|
+
note = asyncio.run(mcp_read_note.fn(identifier, project_name, page, page_size))
|
|
107
149
|
rprint(note)
|
|
108
150
|
except Exception as e: # pragma: no cover
|
|
109
151
|
if not isinstance(e, typer.Exit):
|
|
@@ -115,6 +157,10 @@ def read_note(identifier: str, page: int = 1, page_size: int = 10):
|
|
|
115
157
|
@tool_app.command()
|
|
116
158
|
def build_context(
|
|
117
159
|
url: MemoryUrl,
|
|
160
|
+
project: Annotated[
|
|
161
|
+
Optional[str],
|
|
162
|
+
typer.Option(help="The project to use. If not provided, the default project will be used."),
|
|
163
|
+
] = None,
|
|
118
164
|
depth: Optional[int] = 1,
|
|
119
165
|
timeframe: Optional[TimeFrame] = "7d",
|
|
120
166
|
page: int = 1,
|
|
@@ -122,9 +168,23 @@ def build_context(
|
|
|
122
168
|
max_related: int = 10,
|
|
123
169
|
):
|
|
124
170
|
"""Get context needed to continue a discussion."""
|
|
171
|
+
|
|
172
|
+
# look for the project in the config
|
|
173
|
+
config_manager = ConfigManager()
|
|
174
|
+
project_name = None
|
|
175
|
+
if project is not None:
|
|
176
|
+
project_name, _ = config_manager.get_project(project)
|
|
177
|
+
if not project_name:
|
|
178
|
+
typer.echo(f"No project found named: {project}", err=True)
|
|
179
|
+
raise typer.Exit(1)
|
|
180
|
+
|
|
181
|
+
# use the project name, or the default from the config
|
|
182
|
+
project_name = project_name or config_manager.default_project
|
|
183
|
+
|
|
125
184
|
try:
|
|
126
185
|
context = asyncio.run(
|
|
127
186
|
mcp_build_context.fn(
|
|
187
|
+
project=project_name,
|
|
128
188
|
url=url,
|
|
129
189
|
depth=depth,
|
|
130
190
|
timeframe=timeframe,
|
|
@@ -150,30 +210,21 @@ def recent_activity(
|
|
|
150
210
|
type: Annotated[Optional[List[SearchItemType]], typer.Option()] = None,
|
|
151
211
|
depth: Optional[int] = 1,
|
|
152
212
|
timeframe: Optional[TimeFrame] = "7d",
|
|
153
|
-
page: int = 1,
|
|
154
|
-
page_size: int = 10,
|
|
155
|
-
max_related: int = 10,
|
|
156
213
|
):
|
|
157
214
|
"""Get recent activity across the knowledge base."""
|
|
158
215
|
try:
|
|
159
|
-
|
|
216
|
+
result = asyncio.run(
|
|
160
217
|
mcp_recent_activity.fn(
|
|
161
218
|
type=type, # pyright: ignore [reportArgumentType]
|
|
162
219
|
depth=depth,
|
|
163
220
|
timeframe=timeframe,
|
|
164
|
-
page=page,
|
|
165
|
-
page_size=page_size,
|
|
166
|
-
max_related=max_related,
|
|
167
221
|
)
|
|
168
222
|
)
|
|
169
|
-
#
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
context_dict = context.model_dump(exclude_none=True)
|
|
173
|
-
print(json.dumps(context_dict, indent=2, ensure_ascii=True, default=str))
|
|
223
|
+
# The tool now returns a formatted string directly
|
|
224
|
+
print(result)
|
|
174
225
|
except Exception as e: # pragma: no cover
|
|
175
226
|
if not isinstance(e, typer.Exit):
|
|
176
|
-
typer.echo(f"Error during
|
|
227
|
+
typer.echo(f"Error during recent_activity: {e}", err=True)
|
|
177
228
|
raise typer.Exit(1)
|
|
178
229
|
raise
|
|
179
230
|
|
|
@@ -183,6 +234,12 @@ def search_notes(
|
|
|
183
234
|
query: str,
|
|
184
235
|
permalink: Annotated[bool, typer.Option("--permalink", help="Search permalink values")] = False,
|
|
185
236
|
title: Annotated[bool, typer.Option("--title", help="Search title values")] = False,
|
|
237
|
+
project: Annotated[
|
|
238
|
+
Optional[str],
|
|
239
|
+
typer.Option(
|
|
240
|
+
help="The project to use for the note. If not provided, the default project will be used."
|
|
241
|
+
),
|
|
242
|
+
] = None,
|
|
186
243
|
after_date: Annotated[
|
|
187
244
|
Optional[str],
|
|
188
245
|
typer.Option("--after_date", help="Search results after date, eg. '2d', '1 week'"),
|
|
@@ -191,6 +248,19 @@ def search_notes(
|
|
|
191
248
|
page_size: int = 10,
|
|
192
249
|
):
|
|
193
250
|
"""Search across all content in the knowledge base."""
|
|
251
|
+
|
|
252
|
+
# look for the project in the config
|
|
253
|
+
config_manager = ConfigManager()
|
|
254
|
+
project_name = None
|
|
255
|
+
if project is not None:
|
|
256
|
+
project_name, _ = config_manager.get_project(project)
|
|
257
|
+
if not project_name:
|
|
258
|
+
typer.echo(f"No project found named: {project}", err=True)
|
|
259
|
+
raise typer.Exit(1)
|
|
260
|
+
|
|
261
|
+
# use the project name, or the default from the config
|
|
262
|
+
project_name = project_name or config_manager.default_project
|
|
263
|
+
|
|
194
264
|
if permalink and title: # pragma: no cover
|
|
195
265
|
print("Cannot search both permalink and title")
|
|
196
266
|
raise typer.Abort()
|
|
@@ -212,6 +282,7 @@ def search_notes(
|
|
|
212
282
|
results = asyncio.run(
|
|
213
283
|
mcp_search.fn(
|
|
214
284
|
query,
|
|
285
|
+
project_name,
|
|
215
286
|
search_type=search_type,
|
|
216
287
|
page=page,
|
|
217
288
|
after_date=after_date,
|