feat(cli): expose project sync support metadata

Signed-off-by: Drew Cain <groksrc@gmail.com>
This commit is contained in:
Drew Cain
2026-05-26 13:03:34 -05:00
committed by Paul Hernandez
parent 8bf7bdbc0d
commit f07643d3a8
4 changed files with 73 additions and 8 deletions
+18 -6
View File
@@ -617,7 +617,13 @@ def list_projects(
)
is_default = bool(is_attached_row and permalink == default_permalink)
sync_supported = cloud_workspace is None or cloud_workspace.workspace_type == "personal"
# Show workspace name (type) for cloud-sourced projects
cloud_ws_name = cloud_workspace.name if cloud_workspace else None
cloud_ws_type = cloud_workspace.workspace_type if cloud_workspace else None
sync_supported = cloud_ws_type is None or cloud_ws_type == "personal"
sync_reason = None if sync_supported else f"{cloud_ws_type} workspace"
local_usage = "sync-supported" if sync_supported else "cloud-only"
has_sync = bool(is_attached_row and entry and entry.local_sync_path and sync_supported)
# Determine MCP transport based on project routing mode
if entry and entry.mode == ProjectMode.CLOUD:
@@ -627,10 +633,6 @@ def list_projects(
else:
mcp_transport = "stdio"
# Show workspace name (type) for cloud-sourced projects
cloud_ws_name = cloud_workspace.name if cloud_workspace else None
cloud_ws_type = cloud_workspace.workspace_type if cloud_workspace else None
# display_name is a human label for private UUID-named projects (e.g., "My Project").
# Keep "name" as the canonical identifier for scripting/JSON consumers;
# the Rich table uses display_name when available.
@@ -645,6 +647,9 @@ def list_projects(
"cli_route": cli_route,
"mcp_stdio": mcp_transport,
"sync": has_sync,
"sync_supported": sync_supported,
"sync_reason": sync_reason,
"local_usage": local_usage,
"is_default": is_default,
}
if display_name:
@@ -663,6 +668,13 @@ def list_projects(
# --- Rich table output ---
for row_data in project_rows:
sync_display = (
"[X]"
if row_data["sync"]
else "cloud-only"
if not row_data["sync_supported"]
else ""
)
table.add_row(
row_data.get("display_name") or row_data["name"],
row_data["local_path"],
@@ -671,7 +683,7 @@ def list_projects(
+ (f" ({row_data['workspace_type']})" if row_data.get("workspace_type") else ""),
row_data["cli_route"],
row_data["mcp_stdio"],
"[X]" if row_data["sync"] else "",
sync_display,
"[X]" if row_data["is_default"] else "",
)
@@ -35,6 +35,16 @@ from basic_memory.utils import generate_permalink
# --- Helpers for dual-fetch + merge ---
def _sync_support_metadata(workspace_type: str | None) -> dict[str, object]:
"""Return structured local sync support fields for project listings."""
sync_supported = workspace_type is None or workspace_type == "personal"
return {
"sync_supported": sync_supported,
"sync_reason": None if sync_supported else f"{workspace_type} workspace",
"local_usage": "sync-supported" if sync_supported else "cloud-only",
}
def _merge_projects(
local_list: ProjectList | None,
cloud_list: ProjectList | None,
@@ -125,6 +135,7 @@ def _merge_projects(
"workspace_tenant_id": ws_tenant_id,
"workspace_slug": cloud_workspace_slug if cloud_proj else None,
"workspace_is_default": cloud_workspace_is_default if cloud_proj else False,
**_sync_support_metadata(ws_type),
"qualified_name": (
f"{cloud_workspace_slug}/{permalink}"
if cloud_proj and cloud_workspace_slug
@@ -258,6 +269,7 @@ def _merge_workspace_projects(
"workspace_tenant_id": entry.workspace.tenant_id,
"workspace_slug": entry.workspace.slug,
"workspace_is_default": entry.workspace.is_default,
**_sync_support_metadata(entry.workspace.workspace_type),
"qualified_name": entry.qualified_name,
}
)
@@ -282,6 +294,7 @@ def _merge_workspace_projects(
"workspace_tenant_id": None,
"workspace_slug": None,
"workspace_is_default": False,
**_sync_support_metadata(None),
"qualified_name": None,
}
)
@@ -313,7 +326,10 @@ def _format_project_list_text(merged: list[dict]) -> str:
source = project["source"]
external_id = project.get("external_id", "")
id_suffix = f" [{external_id}]" if external_id else ""
result += f"- {label} ({source}){id_suffix}\n"
usage_suffix = ""
if project.get("sync_supported") is False:
usage_suffix = " - cloud-only (local sync unsupported)"
result += f"- {label} ({source}){id_suffix}{usage_suffix}\n"
result += "\n" + "" * 40 + "\n"
result += "Next: Ask which project to use for this session.\n"