Skip to content

feat(cli): add Rich human-readable output to bm tool commands#967

Merged
phernandez merged 15 commits into
mainfrom
feat/678-rich-tool-output
Jul 15, 2026
Merged

feat(cli): add Rich human-readable output to bm tool commands#967
phernandez merged 15 commits into
mainfrom
feat/678-rich-tool-output

Conversation

@groksrc

@groksrc groksrc commented Jun 11, 2026

Copy link
Copy Markdown
Member

Closes #678

Summary

Adds human-readable output to bm tool search-notes, read-note, build-context, and recent-activity while preserving machine-readable behavior.

Mode Selection
JSON --json, or stdout is piped/redirected
Plain --plain
Rich Interactive TTY default

--json --plain is rejected. Interactive users can set cli_output_style: "rich" | "plain"; the default remains Rich.

Renderers

  • search-notes renders a result table with scores and snippets.
  • read-note renders Markdown and optional frontmatter in Rich mode; plain frontmatter mode writes the literal file byte-for-byte for redirection.
  • build-context renders primary entities, observations, and related results as a tree.
  • recent-activity renders a compact activity table.
  • User-sourced Rich text is escaped so bracketed titles, categories, snippets, and metadata remain literal.
  • Missing notes render a clear not-found result with related suggestions in Rich and plain modes; byte-faithful plain frontmatter output still emits no bytes on a miss.

The existing --include-frontmatter CLI spelling remains as a deprecated alias for the shorter --frontmatter; the MCP parameter is unchanged.

Maintainer follow-through

  • Rebased onto current main and updated tests to patch the deferred MCP import targets.
  • Made --plain --frontmatter preserve all boundary newlines with exact-output coverage.
  • Coalesced nullable read-note fields and added not-found rendering with markup-safe suggestions.
  • Distinguished a missing note from an empty note in plain mode while keeping missing --plain --frontmatter output byte-faithful.

Testing

  • uv run pytest -q tests/cli/test_cli_tool_rich_output.py — 46 passed.
  • All related CLI-tool tests — 88 passed.
  • Ruff and targeted ty — passed.
  • just fast-check — passed (with the repository's existing Python 3.14 asyncio deprecation diagnostics).

Manual follow-up

  • manual/man3/read-note-3 already documents --frontmatter and its deprecated alias.
  • At merge, expand the manual with the JSON/plain/Rich output-mode story and cli_output_style.

Generated with Claude Code; maintainer hardening by Basic Machines.

@groksrc
groksrc marked this pull request as ready for review June 11, 2026 07:27

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 457cd0ed3b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/basic_memory/cli/commands/tool.py Outdated
Comment thread src/basic_memory/cli/commands/tool.py
@groksrc

groksrc commented Jun 12, 2026

Copy link
Copy Markdown
Member Author

--plain output mode + cli_output_style config default

Extends the rich-tool-output work with a third output mode and a configurable interactive default for the four formatted bm tool commands (search-notes, read-note, build-context, recent-activity).

Three output modes

  • JSON — raw machine-readable output. Used with --json, or automatically when stdout is piped/redirected (unchanged script compatibility).
  • Rich — colored Panel/Table/Tree/Markdown. The out-of-the-box TTY experience.
  • Plain — undecorated, greppable text: no ANSI colors, no box-drawing, no markup. Forced with --plain even when piped.
    • search → numbered results with title/score/permalink + indented snippet
    • read-note → title [permalink] header, optional key: value frontmatter (only with --include-frontmatter), then the raw markdown body
    • build-context → ASCII-indented outline (primary, then 2-space-indented observations and related items with relation types)
    • recent-activity → - title (type) permalink updated lines

Plain renderers intentionally do not apply rich.markup.escape (that's only correct on the Rich path and would corrupt literal brackets), so [draft]/[fact] survive verbatim.

Precedence (highest first)

  1. --json — wins over everything
  2. --plain — forces plain, even when piped
  3. non-TTY stdout → JSON (script compatibility)
  4. TTY → config cli_output_style (rich by default)

Passing both --json and --plain is a typer error with a clear message and non-zero exit.

New config option

BasicMemoryConfig.cli_output_style: Literal["rich", "plain"] = "rich" (env BASIC_MEMORY_CLI_OUTPUT_STYLE) sets the interactive TTY default. It follows the existing CLI-behavior field conventions; since the repo has no general CLI-settings reference doc, the Field description documents the setting (same as write_note_overwrite_default).

Tests / gates

  • Per-command plain renderers (incl. literal [draft]/[fact] survival and the total=0 count fallback in plain mode).
  • Precedence matrix: --json alone → JSON; --plain forces plain when piped; non-TTY default still JSON; TTY+config rich → Rich; TTY+config plain → plain; --json --plain together → non-zero error.
  • uv run pytest tests/cli/test_cli_tool_rich_output.py tests/cli/test_cli_tools.py green (42 passed); JSON-output/exit/config suites green; ruff check + format --check clean; uv run ty check src tests test-int passes.

🤖 Generated with Claude Code

@groksrc

groksrc commented Jun 12, 2026

Copy link
Copy Markdown
Member Author

This is good to go, but needs to ship in v0.next

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds human-friendly terminal output to key bm tool subcommands while preserving script-friendly JSON behavior, introducing a centralized output-mode resolver and a new config knob to control interactive defaults.

Changes:

  • Adds Rich (Panels/Tables/Trees/Markdown) and --plain output modes to bm tool commands with a precedence matrix and mutual-exclusion validation for --json/--plain.
  • Introduces cli_output_style: "rich" | "plain" config option (and env override) to control the default interactive output style when stdout is a TTY.
  • Adds a comprehensive new test suite covering mode precedence, escaping, frontmatter handling, and known API quirks.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
src/basic_memory/cli/commands/tool.py Implements output mode resolution plus Rich/plain renderers for search-notes, read-note, build-context, and recent-activity.
src/basic_memory/config.py Adds the cli_output_style configuration field and description for interactive CLI defaults.
tests/cli/test_cli_tool_rich_output.py Adds extensive tests for Rich/plain/JSON behavior, flag precedence, escaping, and frontmatter rendering rules.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/basic_memory/cli/commands/tool.py Outdated
Comment thread tests/cli/test_cli_tool_rich_output.py Outdated
Comment thread tests/cli/test_cli_tool_rich_output.py Outdated
Comment thread tests/cli/test_cli_tool_rich_output.py Outdated
Comment thread tests/cli/test_cli_tool_rich_output.py
@phernandez phernandez removed the On Hold Don't review or merge. Work is pending label Jul 15, 2026
@phernandez
phernandez force-pushed the feat/678-rich-tool-output branch from 83f6005 to af94435 Compare July 15, 2026 02:47
@phernandez

Copy link
Copy Markdown
Member

@codex review

@phernandez

Copy link
Copy Markdown
Member

recheck

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: af944354ea

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/basic_memory/cli/commands/tool.py Outdated
@phernandez

Copy link
Copy Markdown
Member

@codex review

@phernandez

Copy link
Copy Markdown
Member

recheck

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cea9aa922b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/basic_memory/cli/commands/tool.py

Copy link
Copy Markdown
Member

@codex review

Copy link
Copy Markdown
Member

recheck

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3d2dfee1f0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/basic_memory/cli/commands/tool.py
Comment thread src/basic_memory/cli/commands/tool.py
groksrc and others added 9 commits July 14, 2026 23:37
search-notes, read-note, build-context, and recent-activity now display
formatted Rich output (tables, panels, Markdown rendering) when stdout is
an interactive TTY.  When piped or redirected the commands continue to emit
raw JSON exactly as before.  A new --json flag is available on each command
to force JSON output even in a TTY.

Follows the bm status / bm project list precedent: Rich by default for
humans, JSON for machines.

Closes #678

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Drew Cain <groksrc@gmail.com>
…Result shape

_display_build_context was reading top-level item.get("title")/item.get("type")
on each results[i], but the real GraphContext.model_dump() shape wraps every item
as a ContextResult with primary_result + related_results nested inside.  This
made every related note render as an empty tree node.

Fix:
- Rewrite _display_build_context to iterate context_items, build a primary-result
  node from item["primary_result"], then add each item["related_results"] entry as
  a child with relation_type/type/title rendered.
- Update _display_search_results to use the real SearchResponse key "current_page"
  (not "page"), pass query from the CLI argument, add Score and Snippet columns
  (score + matched_chunk/content truncated to 200 chars) as the issue requested.
- Update test fixtures in test_cli_tool_rich_output.py to match the real payload
  shapes (nested ContextResult, current_page, score/matched_chunk fields, no
  updated_at in recent-activity).
- Fix test_build_context_json_flag_overrides_tty assertion to navigate the nested
  primary_result/related_results shape.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Drew Cain <groksrc@gmail.com>
MCP tools return str | dict depending on output_format; the Rich display
helpers require the dict (or list) shape. String payloads keep main's
behavior of printing through the JSON path.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Drew Cain <groksrc@gmail.com>
- `_display_read_note`: render frontmatter key/value panel when present so
  `--include-frontmatter` is not silently dropped in the Rich path
- `_display_build_context`: render ContextResult.observations under each
  primary node (category + truncated content) so interactive users see the
  same core facts as `--json` output; update subtitle to include count
- Update BUILD_CONTEXT_RESULT fixture with real ObservationSummary shape
  (type/category/content/permalink/file_path/created_at)
- Add test_read_note_rich_include_frontmatter asserting frontmatter keys appear
- Add test_build_context_rich_renders_observations asserting category/content visible

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Drew Cain <groksrc@gmail.com>
…er flag gate

Bug 1 — Rich markup injection: user-sourced titles, permalinks, snippets,
observation categories/content, and frontmatter keys/values were interpolated
directly into Rich markup strings, causing bracketed text (e.g. "[draft]",
"[fact]") to be silently swallowed or restyled.  Apply markup_escape() at
every injection point in _display_search_results, _display_read_note,
_display_build_context, and _display_recent_activity.  Observation labels use
markup_escape on the full "[category] content" fragment so the literal brackets
around the category are also escaped.

Bug 2 — frontmatter panel ignores flag: _display_read_note rendered the
frontmatter panel whenever result["frontmatter"] was non-empty, but the JSON
payload always carries that key regardless of --include-frontmatter.  Thread
the boolean flag as a keyword argument and gate the panel on both the flag and
non-empty content.

Bug 3 — "0 result(s)" subtitle: the search API returns total=0 even when
results is non-empty.  result.get("total", len(results)) never triggered its
default because the key exists; fall back to len(results) when total is falsy
but results is non-empty, keeping page-count math consistent.

Tests: add cases for bracketed title surviving search output, "[fact]" category
surviving build-context tree, read-note without --include-frontmatter asserting
no frontmatter panel, and search with total=0 asserting correct subtitle count.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Drew Cain <groksrc@gmail.com>
The four interactive bm tool commands (search-notes, read-note,
build-context, recent-activity) now support three output modes instead of
two:

- JSON  — raw machine-readable output (--json, or automatically when piped).
- Rich  — colored Panel/Table/Tree/Markdown (the default TTY experience).
- Plain — undecorated, greppable text with no ANSI colors, box-drawing, or
          markup (--plain, forced even when piped).

Precedence, highest first: --json > --plain > non-TTY (JSON) > TTY (config
style).  Passing both --json and --plain is a typer error with a non-zero
exit.  Each TTY default is governed by the new BasicMemoryConfig field
cli_output_style ("rich"/"plain", default "rich", env
BASIC_MEMORY_CLI_OUTPUT_STYLE), mirroring the existing CLI-behavior config
conventions; its Field description documents the setting since the repo has
no general CLI-settings reference doc.

Plain renderers deliberately do NOT apply rich.markup.escape — escaping is
only correct on the Rich path and would corrupt literal brackets — so
[draft]/[fact] survive verbatim.  The plain search path keeps the total=0
fallback so the corrected result count matches the Rich path.  String tool
results still route to JSON to preserve ty narrowing.

Module docstring and each command's help text now document the three modes
and the config default.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Drew Cain <groksrc@gmail.com>
The API content field keeps the blank line left by frontmatter stripping;
plain print() rendered it as a double gap under the header. JSON mode stays
byte-faithful.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Drew Cain <groksrc@gmail.com>
With the flag the API returns the literal file as content, so both display
paths printed the frontmatter twice (synthesized block/panel + the block
inside content). Plain now prints the file verbatim; Rich keeps the panel
and strips the block from the Markdown body.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Drew Cain <groksrc@gmail.com>
…de-frontmatter

The file's own frontmatter carries title/permalink, so the header line
duplicated it. Plain without the flag keeps the header.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Drew Cain <groksrc@gmail.com>
groksrc and others added 5 commits July 14, 2026 23:37
Drop the synthesized title/permalink header and the (no content)
placeholder: plain mode is the content verbatim (note body, or the literal
file with --include-frontmatter). Decoration belongs to the Rich path.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Drew Cain <groksrc@gmail.com>
Keep --include-frontmatter as a deprecated alias for back-compat (the old
name shipped in 0.22.0). The MCP tool parameter include_frontmatter is
unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Drew Cain <groksrc@gmail.com>
Signed-off-by: phernandez <paul@basicmachines.co>
Signed-off-by: phernandez <paul@basicmachines.co>
Signed-off-by: phernandez <paul@basicmachines.co>
@phernandez

Copy link
Copy Markdown
Member

@codex review

recheck

@phernandez
phernandez force-pushed the feat/678-rich-tool-output branch from 3d2dfee to e72a5b2 Compare July 15, 2026 04:38
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

Unknown error
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e72a5b2b91

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/basic_memory/cli/commands/tool.py Outdated
Signed-off-by: phernandez <paul@basicmachines.co>
@phernandez

Copy link
Copy Markdown
Member

@codex review
/recheck

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Nice work!

Reviewed commit: 25fbae7a99

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@phernandez
phernandez merged commit 45b1c71 into main Jul 15, 2026
29 checks passed
@phernandez
phernandez deleted the feat/678-rich-tool-output branch July 15, 2026 05:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CLI: Human-readable output for bm tool commands (demo-ready formatting)

3 participants