basic-memory 0.8.0__py3-none-any.whl → 0.10.0__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/migrations.py +4 -9
- basic_memory/alembic/versions/cc7172b46608_update_search_index_schema.py +106 -0
- basic_memory/api/app.py +9 -6
- basic_memory/api/routers/__init__.py +2 -1
- basic_memory/api/routers/knowledge_router.py +30 -4
- basic_memory/api/routers/memory_router.py +3 -2
- basic_memory/api/routers/project_info_router.py +274 -0
- basic_memory/api/routers/search_router.py +22 -4
- basic_memory/cli/app.py +54 -3
- basic_memory/cli/commands/__init__.py +15 -2
- basic_memory/cli/commands/db.py +9 -13
- basic_memory/cli/commands/import_chatgpt.py +31 -36
- basic_memory/cli/commands/import_claude_conversations.py +32 -35
- basic_memory/cli/commands/import_claude_projects.py +34 -37
- basic_memory/cli/commands/import_memory_json.py +26 -28
- basic_memory/cli/commands/mcp.py +7 -1
- basic_memory/cli/commands/project.py +119 -0
- basic_memory/cli/commands/project_info.py +167 -0
- basic_memory/cli/commands/status.py +7 -9
- basic_memory/cli/commands/sync.py +54 -9
- basic_memory/cli/commands/{tools.py → tool.py} +92 -19
- basic_memory/cli/main.py +40 -1
- basic_memory/config.py +157 -10
- basic_memory/db.py +19 -4
- basic_memory/deps.py +10 -3
- basic_memory/file_utils.py +34 -18
- basic_memory/markdown/markdown_processor.py +1 -1
- basic_memory/markdown/utils.py +5 -0
- basic_memory/mcp/main.py +1 -2
- basic_memory/mcp/prompts/__init__.py +6 -2
- basic_memory/mcp/prompts/ai_assistant_guide.py +9 -10
- basic_memory/mcp/prompts/continue_conversation.py +65 -126
- basic_memory/mcp/prompts/recent_activity.py +55 -13
- basic_memory/mcp/prompts/search.py +72 -17
- basic_memory/mcp/prompts/utils.py +139 -82
- basic_memory/mcp/server.py +1 -1
- basic_memory/mcp/tools/__init__.py +11 -22
- basic_memory/mcp/tools/build_context.py +85 -0
- basic_memory/mcp/tools/canvas.py +17 -19
- basic_memory/mcp/tools/delete_note.py +28 -0
- basic_memory/mcp/tools/project_info.py +51 -0
- basic_memory/mcp/tools/{resource.py → read_content.py} +42 -5
- basic_memory/mcp/tools/read_note.py +190 -0
- basic_memory/mcp/tools/recent_activity.py +100 -0
- basic_memory/mcp/tools/search.py +56 -17
- basic_memory/mcp/tools/utils.py +245 -17
- basic_memory/mcp/tools/write_note.py +124 -0
- basic_memory/models/search.py +2 -1
- basic_memory/repository/entity_repository.py +3 -2
- basic_memory/repository/project_info_repository.py +9 -0
- basic_memory/repository/repository.py +23 -6
- basic_memory/repository/search_repository.py +33 -10
- basic_memory/schemas/__init__.py +12 -0
- basic_memory/schemas/memory.py +3 -2
- basic_memory/schemas/project_info.py +96 -0
- basic_memory/schemas/search.py +27 -32
- basic_memory/services/context_service.py +3 -3
- basic_memory/services/entity_service.py +8 -2
- basic_memory/services/file_service.py +107 -57
- basic_memory/services/link_resolver.py +5 -45
- basic_memory/services/search_service.py +45 -16
- basic_memory/sync/sync_service.py +274 -39
- basic_memory/sync/watch_service.py +174 -34
- basic_memory/utils.py +40 -40
- basic_memory-0.10.0.dist-info/METADATA +386 -0
- basic_memory-0.10.0.dist-info/RECORD +99 -0
- basic_memory/mcp/prompts/json_canvas_spec.py +0 -25
- basic_memory/mcp/tools/knowledge.py +0 -68
- basic_memory/mcp/tools/memory.py +0 -177
- basic_memory/mcp/tools/notes.py +0 -201
- basic_memory-0.8.0.dist-info/METADATA +0 -379
- basic_memory-0.8.0.dist-info/RECORD +0 -91
- {basic_memory-0.8.0.dist-info → basic_memory-0.10.0.dist-info}/WHEEL +0 -0
- {basic_memory-0.8.0.dist-info → basic_memory-0.10.0.dist-info}/entry_points.txt +0 -0
- {basic_memory-0.8.0.dist-info → basic_memory-0.10.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
"""CLI commands for basic-memory."""
|
|
2
2
|
|
|
3
|
-
from . import status, sync, db, import_memory_json, mcp
|
|
3
|
+
from . import status, sync, db, import_memory_json, mcp, import_claude_conversations
|
|
4
|
+
from . import import_claude_projects, import_chatgpt, tool, project, project_info
|
|
4
5
|
|
|
5
|
-
__all__ = [
|
|
6
|
+
__all__ = [
|
|
7
|
+
"status",
|
|
8
|
+
"sync",
|
|
9
|
+
"db",
|
|
10
|
+
"import_memory_json",
|
|
11
|
+
"mcp",
|
|
12
|
+
"import_claude_conversations",
|
|
13
|
+
"import_claude_projects",
|
|
14
|
+
"import_chatgpt",
|
|
15
|
+
"tool",
|
|
16
|
+
"project",
|
|
17
|
+
"project_info",
|
|
18
|
+
]
|
basic_memory/cli/commands/db.py
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
"""Database management commands."""
|
|
2
2
|
|
|
3
|
-
import asyncio
|
|
4
|
-
|
|
5
|
-
import logfire
|
|
6
3
|
import typer
|
|
7
4
|
from loguru import logger
|
|
8
5
|
|
|
@@ -12,17 +9,16 @@ from basic_memory.cli.app import app
|
|
|
12
9
|
|
|
13
10
|
@app.command()
|
|
14
11
|
def reset(
|
|
15
|
-
reindex: bool = typer.Option(False, "--reindex", help="Rebuild
|
|
12
|
+
reindex: bool = typer.Option(False, "--reindex", help="Rebuild db index from filesystem"),
|
|
16
13
|
): # pragma: no cover
|
|
17
14
|
"""Reset database (drop all tables and recreate)."""
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
asyncio.run(migrations.reset_database())
|
|
15
|
+
if typer.confirm("This will delete all data in your db. Are you sure?"):
|
|
16
|
+
logger.info("Resetting database...")
|
|
17
|
+
migrations.reset_database()
|
|
22
18
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
if reindex:
|
|
20
|
+
# Import and run sync
|
|
21
|
+
from basic_memory.cli.commands.sync import sync
|
|
26
22
|
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
logger.info("Rebuilding search index from filesystem...")
|
|
24
|
+
sync(watch=False) # pyright: ignore
|
|
@@ -6,17 +6,15 @@ from datetime import datetime
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import Dict, Any, List, Annotated, Set, Optional
|
|
8
8
|
|
|
9
|
-
import logfire
|
|
10
9
|
import typer
|
|
11
|
-
from loguru import logger
|
|
12
|
-
from rich.console import Console
|
|
13
|
-
from rich.panel import Panel
|
|
14
|
-
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
|
|
15
|
-
|
|
16
10
|
from basic_memory.cli.app import import_app
|
|
17
11
|
from basic_memory.config import config
|
|
18
12
|
from basic_memory.markdown import EntityParser, MarkdownProcessor
|
|
19
13
|
from basic_memory.markdown.schemas import EntityMarkdown, EntityFrontmatter
|
|
14
|
+
from loguru import logger
|
|
15
|
+
from rich.console import Console
|
|
16
|
+
from rich.panel import Panel
|
|
17
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
|
|
20
18
|
|
|
21
19
|
console = Console()
|
|
22
20
|
|
|
@@ -168,7 +166,7 @@ async def process_chatgpt_json(
|
|
|
168
166
|
read_task = progress.add_task("Reading chat data...", total=None)
|
|
169
167
|
|
|
170
168
|
# Read conversations
|
|
171
|
-
conversations = json.loads(json_path.read_text())
|
|
169
|
+
conversations = json.loads(json_path.read_text(encoding="utf-8"))
|
|
172
170
|
progress.update(read_task, total=len(conversations))
|
|
173
171
|
|
|
174
172
|
# Process each conversation
|
|
@@ -226,38 +224,35 @@ def import_chatgpt(
|
|
|
226
224
|
After importing, run 'basic-memory sync' to index the new files.
|
|
227
225
|
"""
|
|
228
226
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
if conversations_json:
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
raise typer.Exit(1)
|
|
227
|
+
try:
|
|
228
|
+
if conversations_json:
|
|
229
|
+
if not conversations_json.exists():
|
|
230
|
+
typer.echo(f"Error: File not found: {conversations_json}", err=True)
|
|
231
|
+
raise typer.Exit(1)
|
|
235
232
|
|
|
236
|
-
|
|
237
|
-
|
|
233
|
+
# Get markdown processor
|
|
234
|
+
markdown_processor = asyncio.run(get_markdown_processor())
|
|
238
235
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
)
|
|
244
|
-
|
|
245
|
-
process_chatgpt_json(conversations_json, folder, markdown_processor)
|
|
246
|
-
)
|
|
236
|
+
# Process the file
|
|
237
|
+
base_path = config.home / folder
|
|
238
|
+
console.print(f"\nImporting chats from {conversations_json}...writing to {base_path}")
|
|
239
|
+
results = asyncio.run(
|
|
240
|
+
process_chatgpt_json(conversations_json, folder, markdown_processor)
|
|
241
|
+
)
|
|
247
242
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
)
|
|
243
|
+
# Show results
|
|
244
|
+
console.print(
|
|
245
|
+
Panel(
|
|
246
|
+
f"[green]Import complete![/green]\n\n"
|
|
247
|
+
f"Imported {results['conversations']} conversations\n"
|
|
248
|
+
f"Containing {results['messages']} messages",
|
|
249
|
+
expand=False,
|
|
256
250
|
)
|
|
251
|
+
)
|
|
257
252
|
|
|
258
|
-
|
|
253
|
+
console.print("\nRun 'basic-memory sync' to index the new files.")
|
|
259
254
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
255
|
+
except Exception as e:
|
|
256
|
+
logger.error("Import failed")
|
|
257
|
+
typer.echo(f"Error during import: {e}", err=True)
|
|
258
|
+
raise typer.Exit(1)
|
|
@@ -6,17 +6,15 @@ from datetime import datetime
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import Dict, Any, List, Annotated
|
|
8
8
|
|
|
9
|
-
import logfire
|
|
10
9
|
import typer
|
|
11
|
-
from loguru import logger
|
|
12
|
-
from rich.console import Console
|
|
13
|
-
from rich.panel import Panel
|
|
14
|
-
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
|
|
15
|
-
|
|
16
10
|
from basic_memory.cli.app import claude_app
|
|
17
11
|
from basic_memory.config import config
|
|
18
12
|
from basic_memory.markdown import EntityParser, MarkdownProcessor
|
|
19
13
|
from basic_memory.markdown.schemas import EntityMarkdown, EntityFrontmatter
|
|
14
|
+
from loguru import logger
|
|
15
|
+
from rich.console import Console
|
|
16
|
+
from rich.panel import Panel
|
|
17
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
|
|
20
18
|
|
|
21
19
|
console = Console()
|
|
22
20
|
|
|
@@ -125,7 +123,7 @@ async def process_conversations_json(
|
|
|
125
123
|
read_task = progress.add_task("Reading chat data...", total=None)
|
|
126
124
|
|
|
127
125
|
# Read chat data - handle array of arrays format
|
|
128
|
-
data = json.loads(json_path.read_text())
|
|
126
|
+
data = json.loads(json_path.read_text(encoding="utf-8"))
|
|
129
127
|
conversations = [chat for chat in data]
|
|
130
128
|
progress.update(read_task, total=len(conversations))
|
|
131
129
|
|
|
@@ -179,35 +177,34 @@ def import_claude(
|
|
|
179
177
|
After importing, run 'basic-memory sync' to index the new files.
|
|
180
178
|
"""
|
|
181
179
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
raise typer.Exit(1)
|
|
187
|
-
|
|
188
|
-
# Get markdown processor
|
|
189
|
-
markdown_processor = asyncio.run(get_markdown_processor())
|
|
190
|
-
|
|
191
|
-
# Process the file
|
|
192
|
-
base_path = config.home / folder
|
|
193
|
-
console.print(f"\nImporting chats from {conversations_json}...writing to {base_path}")
|
|
194
|
-
results = asyncio.run(
|
|
195
|
-
process_conversations_json(conversations_json, base_path, markdown_processor)
|
|
196
|
-
)
|
|
180
|
+
try:
|
|
181
|
+
if not conversations_json.exists():
|
|
182
|
+
typer.echo(f"Error: File not found: {conversations_json}", err=True)
|
|
183
|
+
raise typer.Exit(1)
|
|
197
184
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
185
|
+
# Get markdown processor
|
|
186
|
+
markdown_processor = asyncio.run(get_markdown_processor())
|
|
187
|
+
|
|
188
|
+
# Process the file
|
|
189
|
+
base_path = config.home / folder
|
|
190
|
+
console.print(f"\nImporting chats from {conversations_json}...writing to {base_path}")
|
|
191
|
+
results = asyncio.run(
|
|
192
|
+
process_conversations_json(conversations_json, base_path, markdown_processor)
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# Show results
|
|
196
|
+
console.print(
|
|
197
|
+
Panel(
|
|
198
|
+
f"[green]Import complete![/green]\n\n"
|
|
199
|
+
f"Imported {results['conversations']} conversations\n"
|
|
200
|
+
f"Containing {results['messages']} messages",
|
|
201
|
+
expand=False,
|
|
206
202
|
)
|
|
203
|
+
)
|
|
207
204
|
|
|
208
|
-
|
|
205
|
+
console.print("\nRun 'basic-memory sync' to index the new files.")
|
|
209
206
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
207
|
+
except Exception as e:
|
|
208
|
+
logger.error("Import failed")
|
|
209
|
+
typer.echo(f"Error during import: {e}", err=True)
|
|
210
|
+
raise typer.Exit(1)
|
|
@@ -5,17 +5,15 @@ import json
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import Dict, Any, Annotated, Optional
|
|
7
7
|
|
|
8
|
-
import logfire
|
|
9
8
|
import typer
|
|
10
|
-
from loguru import logger
|
|
11
|
-
from rich.console import Console
|
|
12
|
-
from rich.panel import Panel
|
|
13
|
-
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
|
|
14
|
-
|
|
15
9
|
from basic_memory.cli.app import claude_app
|
|
16
10
|
from basic_memory.config import config
|
|
17
11
|
from basic_memory.markdown import EntityParser, MarkdownProcessor
|
|
18
12
|
from basic_memory.markdown.schemas import EntityMarkdown, EntityFrontmatter
|
|
13
|
+
from loguru import logger
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
from rich.panel import Panel
|
|
16
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
|
|
19
17
|
|
|
20
18
|
console = Console()
|
|
21
19
|
|
|
@@ -104,7 +102,7 @@ async def process_projects_json(
|
|
|
104
102
|
read_task = progress.add_task("Reading project data...", total=None)
|
|
105
103
|
|
|
106
104
|
# Read project data
|
|
107
|
-
data = json.loads(json_path.read_text())
|
|
105
|
+
data = json.loads(json_path.read_text(encoding="utf-8"))
|
|
108
106
|
progress.update(read_task, total=len(data))
|
|
109
107
|
|
|
110
108
|
# Track import counts
|
|
@@ -161,36 +159,35 @@ def import_projects(
|
|
|
161
159
|
|
|
162
160
|
After importing, run 'basic-memory sync' to index the new files.
|
|
163
161
|
"""
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if projects_json:
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
expand=False,
|
|
188
|
-
)
|
|
162
|
+
try:
|
|
163
|
+
if projects_json:
|
|
164
|
+
if not projects_json.exists():
|
|
165
|
+
typer.echo(f"Error: File not found: {projects_json}", err=True)
|
|
166
|
+
raise typer.Exit(1)
|
|
167
|
+
|
|
168
|
+
# Get markdown processor
|
|
169
|
+
markdown_processor = asyncio.run(get_markdown_processor())
|
|
170
|
+
|
|
171
|
+
# Process the file
|
|
172
|
+
base_path = config.home / base_folder if base_folder else config.home
|
|
173
|
+
console.print(f"\nImporting projects from {projects_json}...writing to {base_path}")
|
|
174
|
+
results = asyncio.run(
|
|
175
|
+
process_projects_json(projects_json, base_path, markdown_processor)
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# Show results
|
|
179
|
+
console.print(
|
|
180
|
+
Panel(
|
|
181
|
+
f"[green]Import complete![/green]\n\n"
|
|
182
|
+
f"Imported {results['documents']} project documents\n"
|
|
183
|
+
f"Imported {results['prompts']} prompt templates",
|
|
184
|
+
expand=False,
|
|
189
185
|
)
|
|
186
|
+
)
|
|
190
187
|
|
|
191
|
-
|
|
188
|
+
console.print("\nRun 'basic-memory sync' to index the new files.")
|
|
192
189
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
190
|
+
except Exception as e:
|
|
191
|
+
logger.error("Import failed")
|
|
192
|
+
typer.echo(f"Error during import: {e}", err=True)
|
|
193
|
+
raise typer.Exit(1)
|
|
@@ -5,7 +5,6 @@ import json
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import Dict, Any, List, Annotated
|
|
7
7
|
|
|
8
|
-
import logfire
|
|
9
8
|
import typer
|
|
10
9
|
from loguru import logger
|
|
11
10
|
from rich.console import Console
|
|
@@ -114,33 +113,32 @@ def memory_json(
|
|
|
114
113
|
After importing, run 'basic-memory sync' to index the new files.
|
|
115
114
|
"""
|
|
116
115
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
expand=False,
|
|
138
|
-
)
|
|
116
|
+
if not json_path.exists():
|
|
117
|
+
typer.echo(f"Error: File not found: {json_path}", err=True)
|
|
118
|
+
raise typer.Exit(1)
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
# Get markdown processor
|
|
122
|
+
markdown_processor = asyncio.run(get_markdown_processor())
|
|
123
|
+
|
|
124
|
+
# Process the file
|
|
125
|
+
base_path = config.home
|
|
126
|
+
console.print(f"\nImporting from {json_path}...writing to {base_path}")
|
|
127
|
+
results = asyncio.run(process_memory_json(json_path, base_path, markdown_processor))
|
|
128
|
+
|
|
129
|
+
# Show results
|
|
130
|
+
console.print(
|
|
131
|
+
Panel(
|
|
132
|
+
f"[green]Import complete![/green]\n\n"
|
|
133
|
+
f"Created {results['entities']} entities\n"
|
|
134
|
+
f"Added {results['relations']} relations",
|
|
135
|
+
expand=False,
|
|
139
136
|
)
|
|
137
|
+
)
|
|
140
138
|
|
|
141
|
-
|
|
139
|
+
console.print("\nRun 'basic-memory sync' to index the new files.")
|
|
142
140
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
141
|
+
except Exception as e:
|
|
142
|
+
logger.error("Import failed")
|
|
143
|
+
typer.echo(f"Error during import: {e}", err=True)
|
|
144
|
+
raise typer.Exit(1)
|
basic_memory/cli/commands/mcp.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"""MCP server command."""
|
|
2
2
|
|
|
3
3
|
from loguru import logger
|
|
4
|
+
|
|
5
|
+
import basic_memory
|
|
4
6
|
from basic_memory.cli.app import app
|
|
5
7
|
from basic_memory.config import config
|
|
6
8
|
|
|
@@ -15,6 +17,10 @@ import basic_memory.mcp.tools # noqa: F401 # pragma: no cover
|
|
|
15
17
|
def mcp(): # pragma: no cover
|
|
16
18
|
"""Run the MCP server for Claude Desktop integration."""
|
|
17
19
|
home_dir = config.home
|
|
20
|
+
project_name = config.project
|
|
21
|
+
|
|
18
22
|
logger.info(f"Starting Basic Memory MCP server {basic_memory.__version__}")
|
|
19
|
-
logger.info(f"
|
|
23
|
+
logger.info(f"Project: {project_name}")
|
|
24
|
+
logger.info(f"Project directory: {home_dir}")
|
|
25
|
+
|
|
20
26
|
mcp_server.run()
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Command module for basic-memory project management."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
|
|
10
|
+
from basic_memory.cli.app import app
|
|
11
|
+
from basic_memory.config import ConfigManager, config
|
|
12
|
+
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
# Create a project subcommand
|
|
16
|
+
project_app = typer.Typer(help="Manage multiple Basic Memory projects")
|
|
17
|
+
app.add_typer(project_app, name="project")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def format_path(path: str) -> str:
|
|
21
|
+
"""Format a path for display, using ~ for home directory."""
|
|
22
|
+
home = str(Path.home())
|
|
23
|
+
if path.startswith(home):
|
|
24
|
+
return path.replace(home, "~", 1)
|
|
25
|
+
return path
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@project_app.command("list")
|
|
29
|
+
def list_projects() -> None:
|
|
30
|
+
"""List all configured projects."""
|
|
31
|
+
config_manager = ConfigManager()
|
|
32
|
+
projects = config_manager.projects
|
|
33
|
+
|
|
34
|
+
table = Table(title="Basic Memory Projects")
|
|
35
|
+
table.add_column("Name", style="cyan")
|
|
36
|
+
table.add_column("Path", style="green")
|
|
37
|
+
table.add_column("Default", style="yellow")
|
|
38
|
+
table.add_column("Active", style="magenta")
|
|
39
|
+
|
|
40
|
+
default_project = config_manager.default_project
|
|
41
|
+
active_project = config.project
|
|
42
|
+
|
|
43
|
+
for name, path in projects.items():
|
|
44
|
+
is_default = "✓" if name == default_project else ""
|
|
45
|
+
is_active = "✓" if name == active_project else ""
|
|
46
|
+
table.add_row(name, format_path(path), is_default, is_active)
|
|
47
|
+
|
|
48
|
+
console.print(table)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@project_app.command("add")
|
|
52
|
+
def add_project(
|
|
53
|
+
name: str = typer.Argument(..., help="Name of the project"),
|
|
54
|
+
path: str = typer.Argument(..., help="Path to the project directory"),
|
|
55
|
+
) -> None:
|
|
56
|
+
"""Add a new project."""
|
|
57
|
+
config_manager = ConfigManager()
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
# Resolve to absolute path
|
|
61
|
+
resolved_path = os.path.abspath(os.path.expanduser(path))
|
|
62
|
+
config_manager.add_project(name, resolved_path)
|
|
63
|
+
console.print(f"[green]Project '{name}' added at {format_path(resolved_path)}[/green]")
|
|
64
|
+
|
|
65
|
+
# Display usage hint
|
|
66
|
+
console.print("\nTo use this project:")
|
|
67
|
+
console.print(f" basic-memory --project={name} <command>")
|
|
68
|
+
console.print(" # or")
|
|
69
|
+
console.print(f" basic-memory project default {name}")
|
|
70
|
+
except ValueError as e:
|
|
71
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
72
|
+
raise typer.Exit(1)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@project_app.command("remove")
|
|
76
|
+
def remove_project(
|
|
77
|
+
name: str = typer.Argument(..., help="Name of the project to remove"),
|
|
78
|
+
) -> None:
|
|
79
|
+
"""Remove a project from configuration."""
|
|
80
|
+
config_manager = ConfigManager()
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
config_manager.remove_project(name)
|
|
84
|
+
console.print(f"[green]Project '{name}' removed from configuration[/green]")
|
|
85
|
+
console.print("[yellow]Note: The project files have not been deleted from disk.[/yellow]")
|
|
86
|
+
except ValueError as e: # pragma: no cover
|
|
87
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
88
|
+
raise typer.Exit(1)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@project_app.command("default")
|
|
92
|
+
def set_default_project(
|
|
93
|
+
name: str = typer.Argument(..., help="Name of the project to set as default"),
|
|
94
|
+
) -> None:
|
|
95
|
+
"""Set the default project."""
|
|
96
|
+
config_manager = ConfigManager()
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
config_manager.set_default_project(name)
|
|
100
|
+
console.print(f"[green]Project '{name}' set as default[/green]")
|
|
101
|
+
except ValueError as e: # pragma: no cover
|
|
102
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
103
|
+
raise typer.Exit(1)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@project_app.command("current")
|
|
107
|
+
def show_current_project() -> None:
|
|
108
|
+
"""Show the current project."""
|
|
109
|
+
config_manager = ConfigManager()
|
|
110
|
+
current = os.environ.get("BASIC_MEMORY_PROJECT", config_manager.default_project)
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
path = config_manager.get_project_path(current)
|
|
114
|
+
console.print(f"Current project: [cyan]{current}[/cyan]")
|
|
115
|
+
console.print(f"Path: [green]{format_path(str(path))}[/green]")
|
|
116
|
+
console.print(f"Database: [blue]{format_path(str(config.database_path))}[/blue]")
|
|
117
|
+
except ValueError: # pragma: no cover
|
|
118
|
+
console.print(f"[yellow]Warning: Project '{current}' not found in configuration[/yellow]")
|
|
119
|
+
console.print(f"Using default project: [cyan]{config_manager.default_project}[/cyan]")
|