diff --git a/src/basic_memory/cli/commands/tool.py b/src/basic_memory/cli/commands/tool.py index 54fc51fa..8da155e4 100644 --- a/src/basic_memory/cli/commands/tool.py +++ b/src/basic_memory/cli/commands/tool.py @@ -389,28 +389,20 @@ def _plain_search_results(result: dict[str, Any], query: str = "") -> None: print(f" {snippet}") -def _plain_read_note(result: dict[str, Any], *, include_frontmatter: bool = False) -> None: - """Render read-note as a plain title/permalink header, optional frontmatter, then body.""" - title = result.get("title", "") - permalink = result.get("permalink", "") - content = result.get("content", "") +def _plain_read_note(result: dict[str, Any]) -> None: + """Render read-note content faithfully: the note body, or the literal file. - # Trigger: --include-frontmatter makes the API return the literal file - # (frontmatter block included) as content. - # Why: plain mode should show that file verbatim -- the file's own - # frontmatter already carries title/permalink, so a header line and a - # synthesized key/value block would both duplicate it. - # Outcome: with the flag, emit ONLY the file; without it, a title/permalink - # header then the body, trimmed to keep the gap a single blank line. - if not include_frontmatter: - header = f"{title} [{permalink}]" if permalink else title - print(header) - print() + Plain mode adds NO decoration: no header line, no synthesized frontmatter + block, no placeholder for empty notes. Without --include-frontmatter the + API returns the note body; with it, the literal file (frontmatter block + included). Either is printed verbatim, trimmed only of the surrounding + newline artifacts the API keeps from frontmatter stripping, so the output + round-trips (e.g. ``read-note X --plain --include-frontmatter > note.md``). + """ + content = result.get("content", "") body = content.strip("\n") if content else "" if body: print(body) - else: - print("(no content)") def _plain_build_context(result: dict[str, Any]) -> None: @@ -679,7 +671,7 @@ def read_note( if mode == "json" or isinstance(result, str): _print_json(result) elif mode == "plain": - _plain_read_note(result, include_frontmatter=include_frontmatter) + _plain_read_note(result) else: _display_read_note(result, include_frontmatter=include_frontmatter) except ValueError as e: diff --git a/tests/cli/test_cli_tool_rich_output.py b/tests/cli/test_cli_tool_rich_output.py index dfbb2b2c..5082b610 100644 --- a/tests/cli/test_cli_tool_rich_output.py +++ b/tests/cli/test_cli_tool_rich_output.py @@ -755,19 +755,14 @@ def test_search_notes_plain_empty(mock_mcp): return_value=READ_NOTE_RESULT, ) def test_read_note_plain_output(mock_mcp): - """read-note --plain emits a header line and the raw markdown body.""" + """read-note --plain emits the note body faithfully, with no decoration.""" result = _tty_runner(["tool", "read-note", "test-note", "--plain"]) assert result.exit_code == 0, f"CLI failed: {result.output}" - assert "Test Note [notes/test-note]" in result.output - # Raw markdown body, not Rich-rendered - assert "# Test Note" in result.output - assert "hello world" in result.output - # No frontmatter without the flag - assert "tags:" not in result.output - # Exactly one blank line between header and body: the payload's leading - # newline (frontmatter-strip artifact) must not stack with the renderer's. - assert "Test Note [notes/test-note]\n\n# Test Note" in result.output + # No synthesized header line -- plain mode is the payload, faithfully. + assert "[notes/test-note]" not in result.output + # The body verbatim, trimmed of the API's frontmatter-strip newline artifact. + assert result.output == "# Test Note\n\nhello world\n" @patch( @@ -798,11 +793,11 @@ def test_read_note_plain_include_frontmatter(mock_mcp): return_value={"title": "", "permalink": "", "content": "", "frontmatter": {}}, ) def test_read_note_plain_empty_content(mock_mcp): - """read-note --plain handles empty content.""" + """read-note --plain prints nothing for an empty note (no placeholder).""" result = _tty_runner(["tool", "read-note", "empty-note", "--plain"]) assert result.exit_code == 0, f"CLI failed: {result.output}" - assert "(no content)" in result.output + assert result.output == "" @patch(