feat: CLI refactoring + workspace-aware cloud project listing

Refactor CLI commands to use typed ProjectClient instead of raw HTTP calls,
and add workspace metadata to cloud project listings so users can distinguish
personal vs organization projects.

Key changes:
- 🔧 CLI commands now use ProjectClient typed API clients instead of
  call_get/call_post with manual URL construction
- 🏢 Cloud project listings include workspace_name, workspace_type, and
  workspace_tenant_id for each cloud-sourced project
- Pass config.default_workspace when fetching cloud projects via
  _fetch_cloud_projects() and CLI list_projects
- Add --workspace flag to `bm project list` for explicit workspace override
- Add "Workspace" column to CLI project list table
- Add `bm tool list-projects` and `bm tool list-workspaces` JSON commands
- Comprehensive tests for workspace passthrough, merge behavior, and CLI routing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
This commit is contained in:
phernandez
2026-02-21 20:04:03 -06:00
parent 2cde8d2659
commit 2e5813d31e
29 changed files with 1895 additions and 319 deletions
+29 -2
View File
@@ -1,5 +1,7 @@
"""Workspace discovery MCP tool."""
from typing import Literal
from fastmcp import Context
from basic_memory.mcp.project_context import get_available_workspaces
@@ -10,10 +12,35 @@ from basic_memory.mcp.server import mcp
description="List available cloud workspaces (tenant_id, type, role, and name).",
annotations={"readOnlyHint": True, "openWorldHint": False},
)
async def list_workspaces(context: Context | None = None) -> str:
"""List workspaces available to the current cloud user."""
async def list_workspaces(
output_format: Literal["text", "json"] = "text",
context: Context | None = None,
) -> str | dict:
"""List workspaces available to the current cloud user.
Args:
output_format: "text" returns human-readable workspace list.
"json" returns structured workspace metadata.
context: Optional FastMCP context for progress/status logging.
"""
workspaces = await get_available_workspaces(context=context)
if output_format == "json":
return {
"workspaces": [
{
"tenant_id": ws.tenant_id,
"name": ws.name,
"workspace_type": ws.workspace_type,
"role": ws.role,
"organization_id": ws.organization_id,
"has_active_subscription": ws.has_active_subscription,
}
for ws in workspaces
],
"count": len(workspaces),
}
if not workspaces:
return (
"# No Workspaces Available\n\n"