Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/basic_memory/cli/commands/cloud/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ async def get_authenticated_headers(auth: CLIAuth | None = None) -> dict[str, st
auth_obj = auth or CLIAuth(client_id=client_id, authkit_domain=domain)
token = await auth_obj.get_valid_token()
if not token:
console.print("[red]Not authenticated. Please run 'basic-memory cloud login' first.[/red]")
console.print("[red]Not authenticated. Please run 'bm cloud login' first.[/red]")
raise typer.Exit(1)

return {"Authorization": f"Bearer {token}"}
Expand Down
4 changes: 2 additions & 2 deletions src/basic_memory/cli/commands/cloud/core_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ def setup() -> None:
"""Set up cloud sync by installing rclone and configuring credentials.

After setup, use project commands for syncing:
bm project add <name> <path> --local-path ~/projects/<name>
bm project add <name> --cloud --local-path ~/projects/<name>
bm project bisync --name <name> --resync # First time
bm project bisync --name <name> # Subsequent syncs
"""
Expand Down Expand Up @@ -183,7 +183,7 @@ def setup() -> None:
console.print("\n[bold green]Cloud setup completed successfully![/bold green]")
console.print("\n[bold]Next steps:[/bold]")
console.print("1. Add a project with local sync path:")
console.print(" bm project add research --local-path ~/Documents/research")
console.print(" bm project add research --cloud --local-path ~/Documents/research")
console.print("\n Or configure sync for an existing project:")
console.print(" bm project sync-setup research ~/Documents/research")
console.print("\n2. Preview the initial sync (recommended):")
Expand Down
10 changes: 8 additions & 2 deletions src/basic_memory/cli/commands/cloud/upload_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
sync_project,
)
from basic_memory.cli.commands.cloud.upload import upload_path
from basic_memory.mcp.async_client import get_cloud_control_plane_client

console = Console()

Expand Down Expand Up @@ -86,7 +87,7 @@ async def _upload():
console.print(
f"[red]Project '{project}' does not exist.[/red]\n"
f"[yellow]Options:[/yellow]\n"
f" 1. Create it first: bm project add {project}\n"
f" 1. Create it first: bm project add {project} --cloud\n"
f" 2. Use --create-project flag to create automatically"
)
raise typer.Exit(1)
Expand All @@ -100,7 +101,12 @@ async def _upload():
console.print(f"[blue]Uploading {path} to project '{project}'...[/blue]")

success = await upload_path(
path, project, verbose=verbose, use_gitignore=not no_gitignore, dry_run=dry_run
path,
project,
verbose=verbose,
use_gitignore=not no_gitignore,
dry_run=dry_run,
client_cm_factory=get_cloud_control_plane_client,
)
if not success:
console.print("[red]Upload failed[/red]")
Expand Down
16 changes: 15 additions & 1 deletion src/basic_memory/cli/commands/command_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,5 +97,19 @@ async def get_project_info(project: str):
response = await call_get(client, f"/v2/projects/{project_item.external_id}/info")
return ProjectInfoResponse.model_validate(response.json())
except (ToolError, ValueError) as e:
console.print(f"[red]Sync failed: {e}[/red]")
error_text = str(e)
if "internal proxy error" in error_text.lower() and "not found in configuration" in (
error_text.lower()
):
console.print(
"[red]Project info failed: cloud returned an internal configuration error for "
"this project.[/red]"
)
console.print(
"[yellow]This is a cloud backend issue for detailed info lookups. "
"Use `bm project list --cloud` for project metadata until the service is updated."
"[/yellow]"
)
else:
console.print(f"[red]Project info failed: {e}[/red]")
raise typer.Exit(1)
8 changes: 4 additions & 4 deletions src/basic_memory/cli/commands/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,10 +183,10 @@ def format(
By default, formats all .md, .json, and .canvas files in the current project.

Examples:
basic-memory format # Format all files in current project
basic-memory format --project research # Format files in specific project
basic-memory format notes/meeting.md # Format a specific file
basic-memory format notes/ # Format all files in directory
bm format # Format all files in current project
bm format --project research # Format files in specific project
bm format notes/meeting.md # Format a specific file
bm format notes/ # Format all files in directory
"""
try:
run_with_cleanup(run_format(path, project))
Expand Down
4 changes: 2 additions & 2 deletions src/basic_memory/cli/commands/import_chatgpt.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def import_chatgpt(
2. Convert them to linear markdown conversations
3. Save as clean, readable markdown files

After importing, run 'basic-memory sync' to index the new files.
After importing, run 'bm reindex --search' to index the new files.
"""

try:
Expand Down Expand Up @@ -81,7 +81,7 @@ def import_chatgpt(
)
)

console.print("\nRun 'basic-memory sync' to index the new files.")
console.print("\nRun 'bm reindex --search' to index the new files.")

except Exception as e:
logger.error("Import failed")
Expand Down
4 changes: 2 additions & 2 deletions src/basic_memory/cli/commands/import_claude_conversations.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def import_claude(
2. Create markdown files for each conversation
3. Format content in clean, readable markdown

After importing, run 'basic-memory sync' to index the new files.
After importing, run 'bm reindex --search' to index the new files.
"""

config = get_project_config()
Expand Down Expand Up @@ -84,7 +84,7 @@ def import_claude(
)
)

console.print("\nRun 'basic-memory sync' to index the new files.")
console.print("\nRun 'bm reindex --search' to index the new files.")

except Exception as e:
logger.error("Import failed")
Expand Down
4 changes: 2 additions & 2 deletions src/basic_memory/cli/commands/import_claude_projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def import_projects(
2. Store docs in a docs/ subdirectory
3. Place prompt template in project root

After importing, run 'basic-memory sync' to index the new files.
After importing, run 'bm reindex --search' to index the new files.
"""
config = get_project_config()
try:
Expand Down Expand Up @@ -83,7 +83,7 @@ def import_projects(
)
)

console.print("\nRun 'basic-memory sync' to index the new files.")
console.print("\nRun 'bm reindex --search' to index the new files.")

except Exception as e:
logger.error("Import failed")
Expand Down
2 changes: 1 addition & 1 deletion src/basic_memory/cli/commands/mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def mcp(
This command starts an MCP server using one of three transport options:

- stdio: Standard I/O (good for local usage)
- streamable-http: Recommended for web deployments (default)
- streamable-http: Recommended for web deployments
- sse: Server-Sent Events (for compatibility with existing clients)

Initialization, file sync, and cleanup are handled by the MCP server's lifespan.
Expand Down
2 changes: 2 additions & 0 deletions src/basic_memory/cli/commands/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -1149,6 +1149,8 @@ def display_project_info(
)
console.print(f"\nTimestamp: [cyan]{current_time.strftime('%Y-%m-%d %H:%M:%S')}[/cyan]")

except typer.Exit:
raise
except Exception as e: # pragma: no cover
typer.echo(f"Error getting project info: {e}", err=True)
raise typer.Exit(1)
12 changes: 6 additions & 6 deletions src/basic_memory/cli/commands/tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ def write_note(
content: Annotated[
Optional[str],
typer.Option(
help="The content of the note. If not provided, content will be read from stdin. This allows piping content from other commands, e.g.: cat file.md | basic-memory tools write-note"
help="The content of the note. If not provided, content will be read from stdin. This allows piping content from other commands, e.g.: cat file.md | bm tool write-note"
),
] = None,
tags: Annotated[
Expand All @@ -322,13 +322,13 @@ def write_note(
Examples:

# Using content parameter
basic-memory tools write-note --title "My Note" --folder "notes" --content "Note content"
bm tool write-note --title "My Note" --folder "notes" --content "Note content"

# Using stdin pipe
echo "# My Note Content" | basic-memory tools write-note --title "My Note" --folder "notes"
echo "# My Note Content" | bm tool write-note --title "My Note" --folder "notes"

# Using heredoc
cat << EOF | basic-memory tools write-note --title "My Note" --folder "notes"
cat << EOF | bm tool write-note --title "My Note" --folder "notes"
# My Document

This is my document content.
Expand All @@ -338,10 +338,10 @@ def write_note(
EOF

# Reading from a file
cat document.md | basic-memory tools write-note --title "Document" --folder "docs"
cat document.md | bm tool write-note --title "Document" --folder "docs"

# Force local routing in cloud mode
basic-memory tools write-note --title "My Note" --folder "notes" --content "..." --local
bm tool write-note --title "My Note" --folder "notes" --content "..." --local
"""
try:
validate_routing_flags(local, cloud)
Expand Down
17 changes: 14 additions & 3 deletions src/basic_memory/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,21 +476,22 @@ async def run_migrations(
so it's safe to call this multiple times - it will only run pending migrations.
"""
logger.info("Running database migrations...")
temp_engine: AsyncEngine | None = None
try:
revisions_before_upgrade: set[str] = set()
# Trigger: run_migrations() can be invoked before module-level session maker is set.
# Why: we still need reliable before/after revision detection for one-time backfill.
# Outcome: create a short-lived session maker when needed, then dispose it immediately.
if _session_maker is None:
temp_engine, temp_session_maker = _create_engine_and_session(
precheck_engine, temp_session_maker = _create_engine_and_session(
app_config.database_path,
database_type,
app_config,
)
try:
revisions_before_upgrade = await _load_applied_alembic_revisions(temp_session_maker)
finally:
await temp_engine.dispose()
await precheck_engine.dispose()
else:
revisions_before_upgrade = await _load_applied_alembic_revisions(_session_maker)

Expand All @@ -517,7 +518,9 @@ async def run_migrations(

# Get session maker - ensure we don't trigger recursive migration calls
if _session_maker is None:
_, session_maker = _create_engine_and_session(app_config.database_path, database_type)
temp_engine, session_maker = _create_engine_and_session(
app_config.database_path, database_type, app_config
)
else:
session_maker = _session_maker

Expand All @@ -542,3 +545,11 @@ async def run_migrations(
except Exception as e: # pragma: no cover
logger.error(f"Error running migrations: {e}")
raise
finally:
# Trigger: run_migrations() created a temporary engine while module-level
# session maker was not initialized.
# Why: temporary aiosqlite worker threads can outlive CLI command execution
# and block process shutdown if the engine is not disposed.
# Outcome: always dispose temporary engines after migration work completes.
if temp_engine is not None:
await temp_engine.dispose()
4 changes: 1 addition & 3 deletions src/basic_memory/mcp/tools/project_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,7 @@ async def list_memory_projects(
result = "Available projects:\n"
for project in project_list.projects:
label = (
f"{project.display_name} ({project.name})"
if project.display_name
else project.name
f"{project.display_name} ({project.name})" if project.display_name else project.name
)
result += f"• {label}\n"

Expand Down
Loading
Loading