From fc83b036f5261d1dd8e6147c07404a3211bd77cb Mon Sep 17 00:00:00 2001 From: claude-code Date: Fri, 30 Jan 2026 12:38:10 +0000 Subject: [PATCH] Add --list flag to web command for listing sessions Add a --list flag to the web command that displays available sessions grouped by repository without converting them to HTML. This provides a quick way to browse available sessions and get their IDs for export. - Add --list flag to web command options - Implement session listing with repo grouping - Add tests for --list flag functionality - Document --list flag usage in README Implements TDD workflow: - Written failing tests first - Implemented feature to pass tests - All 135 tests passing Co-Authored-By: Claude Sonnet 4.5 --- README.md | 15 ++- src/claude_code_transcripts/__init__.py | 45 +++++++ tests/test_web.py | 149 ++++++++++++++++++++++++ 3 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 tests/test_web.py diff --git a/README.md b/README.md index 25572c9..8bb95e8 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,9 @@ claude-code-transcripts local --limit 20 Import sessions directly from the Claude API: ```bash +# List available web sessions +claude-code-transcripts web --list + # Interactive session picker claude-code-transcripts web @@ -87,7 +90,17 @@ claude-code-transcripts web SESSION_ID claude-code-transcripts web SESSION_ID --gist ``` -The session picker displays sessions grouped by their associated GitHub repository: +Use `--list` to display all available sessions without converting: + +```bash +# List all sessions +claude-code-transcripts web --list + +# List sessions for a specific repo +claude-code-transcripts web --list --repo owner/repo +``` + +The session list displays sessions grouped by their associated GitHub repository: ``` simonw/datasette 2025-01-15T10:30:00 Fix the bug in query parser diff --git a/src/claude_code_transcripts/__init__.py b/src/claude_code_transcripts/__init__.py index e4854a3..6844a0b 100644 --- a/src/claude_code_transcripts/__init__.py +++ b/src/claude_code_transcripts/__init__.py @@ -1983,6 +1983,12 @@ def generate_html_from_session_data(session_data, output_dir, github_repo=None): is_flag=True, help="Open the generated index.html in your default browser (default if no -o specified).", ) +@click.option( + "--list", + "list_only", + is_flag=True, + help="List available sessions and exit without converting.", +) def web_cmd( session_id, output, @@ -1993,6 +1999,7 @@ def web_cmd( gist, include_json, open_browser, + list_only, ): """Select and convert a web session from the Claude API to HTML. @@ -2027,6 +2034,44 @@ def web_cmd( if not sessions: raise click.ClickException(f"No sessions found for repo: {repo}") + # If --list flag is set, display sessions and exit + if list_only: + click.echo(f"\nFound {len(sessions)} session(s):\n") + click.echo("-" * 100) + + # Group by repo + by_repo = {} + for session in sessions: + session_repo = session.get("repo") or "(no repo)" + if session_repo not in by_repo: + by_repo[session_repo] = [] + by_repo[session_repo].append(session) + + # Display sessions grouped by repo + for session_repo in sorted(by_repo.keys()): + click.echo(f"\n{session_repo}") + click.echo("-" * 100) + for s in by_repo[session_repo]: + sid = s.get("id", "N/A") + title = s.get("title", "(no title)") + created_at = s.get("created_at", "") + + # Format the date + date_str = created_at[:19] if created_at else "N/A" + + # Truncate title if too long + if len(title) > 60: + title = title[:57] + "..." + + click.echo(f" {sid} {date_str} {title}") + + click.echo("\n" + "-" * 100) + click.echo(f"\nTotal: {len(sessions)} session(s)") + click.echo( + "\nTo export a session, run: claude-code-transcripts web " + ) + return + # Build choices for questionary choices = [] for s in sessions: diff --git a/tests/test_web.py b/tests/test_web.py new file mode 100644 index 0000000..8c346f9 --- /dev/null +++ b/tests/test_web.py @@ -0,0 +1,149 @@ +"""Tests for web command.""" + +import json +from unittest.mock import Mock, patch + +import pytest +from click.testing import CliRunner + +from claude_code_transcripts import cli + + +class TestWebCommandList: + """Tests for web command --list flag.""" + + @patch("claude_code_transcripts.fetch_sessions") + @patch("claude_code_transcripts.get_access_token_from_keychain") + @patch("claude_code_transcripts.get_org_uuid_from_config") + def test_list_displays_sessions_grouped_by_repo( + self, mock_get_org, mock_get_token, mock_fetch + ): + """Should display sessions grouped by repo when --list flag is used.""" + # Setup mocks + mock_get_token.return_value = "test-token" + mock_get_org.return_value = "test-org-uuid" + + # Mock API response with sessions + mock_fetch.return_value = { + "data": [ + { + "id": "session-123", + "title": "Fix authentication bug", + "created_at": "2026-01-30T10:00:00Z", + "session_context": { + "outcomes": [ + { + "type": "git_repository", + "git_info": {"repo": "owner/repo1", "type": "github"}, + } + ] + }, + }, + { + "id": "session-456", + "title": "Add new feature", + "created_at": "2026-01-29T15:30:00Z", + "session_context": { + "outcomes": [ + { + "type": "git_repository", + "git_info": {"repo": "owner/repo1", "type": "github"}, + } + ] + }, + }, + { + "id": "session-789", + "title": "Update documentation", + "created_at": "2026-01-28T09:15:00Z", + "session_context": {}, + }, + ] + } + + runner = CliRunner() + result = runner.invoke(cli, ["web", "--list"]) + + # Verify exit code + assert result.exit_code == 0 + + # Verify output contains session IDs + assert "session-123" in result.output + assert "session-456" in result.output + assert "session-789" in result.output + + # Verify output contains titles + assert "Fix authentication bug" in result.output + assert "Add new feature" in result.output + assert "Update documentation" in result.output + + # Verify output shows grouping by repo + assert "owner/repo1" in result.output + assert "(no repo)" in result.output + + # Verify count is displayed + assert "3 session(s)" in result.output or "Total: 3" in result.output + + @patch("claude_code_transcripts.fetch_sessions") + @patch("claude_code_transcripts.get_access_token_from_keychain") + @patch("claude_code_transcripts.get_org_uuid_from_config") + def test_list_with_repo_filter(self, mock_get_org, mock_get_token, mock_fetch): + """Should filter sessions by repo when --repo flag is used with --list.""" + mock_get_token.return_value = "test-token" + mock_get_org.return_value = "test-org-uuid" + + mock_fetch.return_value = { + "data": [ + { + "id": "session-123", + "title": "Session in repo1", + "created_at": "2026-01-30T10:00:00Z", + "session_context": { + "outcomes": [ + { + "type": "git_repository", + "git_info": {"repo": "owner/repo1", "type": "github"}, + } + ] + }, + }, + { + "id": "session-456", + "title": "Session in repo2", + "created_at": "2026-01-29T15:30:00Z", + "session_context": { + "outcomes": [ + { + "type": "git_repository", + "git_info": {"repo": "owner/repo2", "type": "github"}, + } + ] + }, + }, + ] + } + + runner = CliRunner() + result = runner.invoke(cli, ["web", "--list", "--repo", "owner/repo1"]) + + assert result.exit_code == 0 + assert "session-123" in result.output + assert "Session in repo1" in result.output + # Should not show session from repo2 + assert "session-456" not in result.output + assert "Session in repo2" not in result.output + + @patch("claude_code_transcripts.fetch_sessions") + @patch("claude_code_transcripts.get_access_token_from_keychain") + @patch("claude_code_transcripts.get_org_uuid_from_config") + def test_list_with_no_sessions(self, mock_get_org, mock_get_token, mock_fetch): + """Should display appropriate message when no sessions found.""" + mock_get_token.return_value = "test-token" + mock_get_org.return_value = "test-org-uuid" + mock_fetch.return_value = {"data": []} + + runner = CliRunner() + result = runner.invoke(cli, ["web", "--list"]) + + assert result.exit_code != 0 + assert "No sessions found" in result.output