diff --git a/calibre/agent-harness/cli_anything/calibre/calibre_cli.py b/calibre/agent-harness/cli_anything/calibre/calibre_cli.py index 67fda52ff..e5a938951 100644 --- a/calibre/agent-harness/cli_anything/calibre/calibre_cli.py +++ b/calibre/agent-harness/cli_anything/calibre/calibre_cli.py @@ -264,15 +264,20 @@ def books_list(ctx, search, sort, limit, fields): if not book_list: click.echo(" No books found.") else: - click.echo(f" {'ID':<5} {'Title':<40} {'Authors':<30} {'Formats'}") - click.echo(f" {'─'*5} {'─'*40} {'─'*30} {'─'*20}") + # Determine columns from actual returned fields (id is always first) + actual_fields = list(book_list[0].keys()) + non_id_fields = [f for f in actual_fields if f != "id"] + + # Build header dynamically + header = f" {'ID':<5} " + " ".join(f"{f.capitalize():<30}" for f in non_id_fields) + sep = f" {'─'*5} " + " ".join(f"{'─'*30}" for _ in non_id_fields) + click.echo(header) + click.echo(sep) for b in book_list: - click.echo( - f" {b.get('id', ''):<5} " - f"{str(b.get('title', ''))[:40]:<40} " - f"{str(b.get('authors', ''))[:30]:<30} " - f"{b.get('formats', '')}" + row = f" {b.get('id', ''):<5} " + " ".join( + f"{str(b.get(f, ''))[:30]:<30}" for f in non_id_fields ) + click.echo(row) @books.command("search") diff --git a/calibre/agent-harness/cli_anything/calibre/tests/test_full_e2e.py b/calibre/agent-harness/cli_anything/calibre/tests/test_full_e2e.py index af57e64c3..9afde7f04 100644 --- a/calibre/agent-harness/cli_anything/calibre/tests/test_full_e2e.py +++ b/calibre/agent-harness/cli_anything/calibre/tests/test_full_e2e.py @@ -199,6 +199,21 @@ class TestLibraryOperations(CalibreTestMixin, unittest.TestCase): self.assertIn("title", book) print(f"\n list_books[0]: {book}") + def test_list_books_custom_fields(self): + """list_books with explicit fields returns exactly those fields per book.""" + from cli_anything.calibre.core.library import list_books + requested = ["id", "title", "tags"] + books = list_books(self.lib_dir, fields=requested) + self.assertGreater(len(books), 0) + book = books[0] + # Requested fields must be present + for field in requested: + self.assertIn(field, book, f"Missing field: {field}") + # Non-requested fields must be absent + for field in ("authors", "series", "rating"): + self.assertNotIn(field, book, f"Unexpected field: {field}") + print(f"\n list_books(fields={requested})[0]: {book}") + def test_search_books(self): from cli_anything.calibre.core.library import search_books ids = search_books(self.lib_dir, "title:Foundation")