Skip to content

Commit 59844d1

Browse files
committed
fix(cli): show configured project in list when uncredentialed (#1003)
`bm project list` seeded its table rows only from live query results — the cloud query (skipped when no credentials are present) and the local query. `config.projects` was used only to enrich existing rows, never to create one, so a cloud-mode project with no cloud credentials on the machine was surfaced by neither query and rendered an empty table. Meanwhile `bm project add` reads the DB and reports the same project already exists, so the two commands disagreed about whether the project existed. Seed a local-keyed row from `config.projects` for any configured project not already surfaced, scoped to the local-inclusive view so a `--cloud` or `--workspace`-filtered listing stays deliberately narrow. Adds a regression test covering the cloud-mode-without-credentials case. Fixes #1003 Signed-off-by: rudi193-cmd <rudi193@gmail.com>
1 parent b5f13d6 commit 59844d1

2 files changed

Lines changed: 59 additions & 0 deletions

File tree

src/basic_memory/cli/commands/project.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,23 @@ def _fetch_cloud_workspace_results() -> tuple[
495495
generate_permalink(project_name): project_name for project_name in config.projects
496496
}
497497

498+
# Trigger: a project in config.projects was surfaced by neither query — the
499+
# cloud branch is skipped without credentials, and a cloud-mode project is
500+
# not returned by the local query.
501+
# Why: without a fallback such a project is invisible in `bm project list`,
502+
# yet `bm project add` reads the DB and reports it already exists (#1003).
503+
# The two commands must agree on whether a configured project exists.
504+
# Outcome: seed a local-keyed row from config so the project still renders;
505+
# the row-building logic below derives its display from the config entry.
506+
# Constraint: only fill from config for the local-inclusive view. A pure
507+
# --cloud listing (no local_result) or a --workspace-filtered view is
508+
# deliberately scoped, so configured local projects must not leak into it.
509+
if local_result is not None and not workspace_filter_requested:
510+
seeded_permalinks = {permalink for _, permalink in row_names_by_key}
511+
for permalink, project_name in configured_names_by_permalink.items():
512+
if permalink not in seeded_permalinks:
513+
row_names_by_key[(None, permalink)] = project_name
514+
498515
def _workspace_priority(row_key: tuple[str | None, str]) -> tuple[bool, int, str, str]:
499516
"""Prefer the user's default/personal workspace when a project is duplicated."""
500517
workspace = cloud_workspaces_by_key.get(row_key)

tests/cli/test_project_list_and_ls.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,3 +1066,45 @@ async def fake_get_mount_info():
10661066
assert result.exit_code == 0, f"Exit code: {result.exit_code}, output: {result.stdout}"
10671067
assert "Files in alpha (CLOUD)" in result.stdout
10681068
assert "cloud.md" in result.stdout
1069+
1070+
1071+
def test_project_list_shows_configured_project_without_cloud_credentials(
1072+
runner: CliRunner, write_config, mock_client, tmp_path, monkeypatch
1073+
):
1074+
"""A cloud-mode project in config must render even with no cloud credentials (#1003).
1075+
1076+
Regression: ``bm project list`` seeded rows only from live query results, so a
1077+
cloud-mode project was skipped by both the credential-gated cloud branch and the
1078+
local query — the table came up empty while ``bm project add`` reported the same
1079+
project already existed. The two commands must agree that the project exists.
1080+
"""
1081+
write_config(
1082+
{
1083+
"env": "dev",
1084+
"projects": {
1085+
"main": {
1086+
"path": "",
1087+
"mode": "cloud",
1088+
"workspace_id": None,
1089+
"local_sync_path": None,
1090+
}
1091+
},
1092+
"default_project": "main",
1093+
}
1094+
)
1095+
1096+
# No credentials on this machine: the cloud branch is skipped entirely.
1097+
monkeypatch.setattr(project_cmd, "_has_cloud_credentials", lambda config: False)
1098+
1099+
# The local query does not surface a cloud-mode project, so it returns empty.
1100+
async def fake_list_projects(self):
1101+
return ProjectList.model_validate({"projects": [], "default_project": "main"})
1102+
1103+
monkeypatch.setattr(ProjectClient, "list_projects", fake_list_projects)
1104+
1105+
result = runner.invoke(app, ["project", "list"], env={"COLUMNS": "240"})
1106+
1107+
assert result.exit_code == 0, f"Exit code: {result.exit_code}, output: {result.stdout}"
1108+
# The configured project is now visible instead of an empty table.
1109+
main_line = next(line for line in result.stdout.splitlines() if "│ main" in line)
1110+
assert "cloud" in main_line # cloud-mode projects route to the cloud CLI

0 commit comments

Comments
 (0)