Skip to content

Commit 86ad639

Browse files
jope-bmclaude
andauthored
fix(cli): show display_name instead of UUID for private projects in CLI (#718)
Signed-off-by: Joe P <joe@basicmemory.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 88c8f18 commit 86ad639

File tree

2 files changed

+85
-1
lines changed

2 files changed

+85
-1
lines changed

src/basic_memory/cli/commands/project.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,12 @@ async def _list_projects(ws: str | None = None):
253253
if cloud_project is not None and cloud_ws_name:
254254
ws_label = f"{cloud_ws_name} ({cloud_ws_type})" if cloud_ws_type else cloud_ws_name
255255

256+
# display_name is a human label for private UUID-named projects (e.g., "My Project").
257+
# Keep "name" as the canonical identifier for scripting/JSON consumers;
258+
# the Rich table uses display_name when available.
259+
display_name = (
260+
cloud_project.display_name if cloud_project and cloud_project.display_name else None
261+
)
256262
row_data = {
257263
"name": project_name,
258264
"permalink": permalink,
@@ -263,6 +269,8 @@ async def _list_projects(ws: str | None = None):
263269
"sync": has_sync,
264270
"is_default": is_default,
265271
}
272+
if display_name:
273+
row_data["display_name"] = display_name
266274
if ws_label:
267275
row_data["workspace"] = cloud_ws_name or ""
268276
if cloud_ws_type:
@@ -278,7 +286,7 @@ async def _list_projects(ws: str | None = None):
278286
# --- Rich table output ---
279287
for row_data in project_rows:
280288
table.add_row(
281-
row_data["name"],
289+
row_data.get("display_name") or row_data["name"],
282290
row_data["local_path"],
283291
row_data["cloud_path"],
284292
row_data.get("workspace", "")

tests/cli/test_project_list_and_ls.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,82 @@ async def fake_list_projects(self):
139139
assert "/beta" in result.stdout
140140

141141

142+
def test_project_list_shows_display_name_for_private_projects(
143+
runner: CliRunner, write_config, mock_client, tmp_path, monkeypatch
144+
):
145+
"""Private projects should show display_name ('My Project') instead of raw UUID name."""
146+
private_uuid = "f1df8f39-d5aa-4095-ae05-8c5a2883029a"
147+
148+
write_config(
149+
{
150+
"env": "dev",
151+
"projects": {},
152+
"default_project": "main",
153+
"cloud_api_key": "bmc_test_key_123",
154+
}
155+
)
156+
157+
local_payload = {
158+
"projects": [
159+
{
160+
"id": 1,
161+
"external_id": "11111111-1111-1111-1111-111111111111",
162+
"name": "main",
163+
"path": "/main",
164+
"is_default": True,
165+
}
166+
],
167+
"default_project": "main",
168+
}
169+
170+
cloud_payload = {
171+
"projects": [
172+
{
173+
"id": 1,
174+
"external_id": "11111111-1111-1111-1111-111111111111",
175+
"name": "main",
176+
"path": "/main",
177+
"is_default": True,
178+
},
179+
{
180+
"id": 2,
181+
"external_id": "22222222-2222-2222-2222-222222222222",
182+
"name": private_uuid,
183+
"path": f"/{private_uuid}",
184+
"is_default": False,
185+
"display_name": "My Project",
186+
"is_private": True,
187+
},
188+
],
189+
"default_project": "main",
190+
}
191+
192+
async def fake_list_projects(self):
193+
if os.getenv("BASIC_MEMORY_FORCE_CLOUD", "").lower() in ("true", "1", "yes"):
194+
return ProjectList.model_validate(cloud_payload)
195+
return ProjectList.model_validate(local_payload)
196+
197+
monkeypatch.setattr(ProjectClient, "list_projects", fake_list_projects)
198+
199+
result = runner.invoke(app, ["project", "list"], env={"COLUMNS": "240"})
200+
201+
assert result.exit_code == 0, f"Exit code: {result.exit_code}, output: {result.stdout}"
202+
# Rich table should show display_name in the Name column
203+
assert "My Project" in result.stdout
204+
lines = result.stdout.splitlines()
205+
project_line = next(line for line in lines if "My Project" in line)
206+
name_cell = project_line.split("│")[1].strip()
207+
assert name_cell == "My Project"
208+
209+
# JSON output should preserve canonical name for scripting, with display_name as separate field
210+
json_result = runner.invoke(app, ["project", "list", "--json"], env={"COLUMNS": "240"})
211+
assert json_result.exit_code == 0
212+
data = json.loads(json_result.stdout)
213+
private_project = next(p for p in data["projects"] if p.get("display_name") == "My Project")
214+
assert private_project["name"] == private_uuid
215+
assert private_project["display_name"] == "My Project"
216+
217+
142218
def test_project_ls_local_mode_defaults_to_local_route(
143219
runner: CliRunner, write_config, mock_client, tmp_path, monkeypatch
144220
):

0 commit comments

Comments
 (0)