Skip to content

Commit a7ca39d

Browse files
committed
1 parent 0e429be commit a7ca39d

File tree

3 files changed

+116
-8
lines changed

3 files changed

+116
-8
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,16 @@ When using [Claude Code for web](https://claude.ai/code) you can export your ses
2727

2828
This tool converts that JSON into a browseable multi-page HTML transcript.
2929

30+
The quickest way to view a recent session is to import it directly and open in your browser:
31+
32+
```bash
33+
claude-code-publish import --open
34+
```
35+
36+
This shows an interactive picker to select a session, generates HTML, and opens it in your default browser.
37+
38+
For a local session file:
39+
3040
```bash
3141
claude-code-publish session.json -o output-directory/
3242
```
@@ -39,6 +49,7 @@ This will generate:
3949

4050
- `-o, --output DIRECTORY` - output directory (default: current directory)
4151
- `--repo OWNER/NAME` - GitHub repo for commit links (auto-detected from git push output if not specified)
52+
- `--open` - open the generated `index.html` in your default browser
4253
- `--gist` - upload the generated HTML files to a GitHub Gist and output a preview URL
4354
- `--json` - include the original JSON session file in the output directory
4455

src/claude_code_publish/__init__.py

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import shutil
99
import subprocess
1010
import tempfile
11+
import webbrowser
1112
from pathlib import Path
1213

1314
import click
@@ -931,7 +932,7 @@ def cli():
931932
"-o",
932933
"--output",
933934
type=click.Path(),
934-
help="Output directory (default: current directory, or temp dir with --gist)",
935+
help="Output directory (default: current directory, or temp dir with --gist/--open)",
935936
)
936937
@click.option(
937938
"--repo",
@@ -948,10 +949,16 @@ def cli():
948949
is_flag=True,
949950
help="Include the original JSON session file in the output directory.",
950951
)
951-
def session(json_file, output, repo, gist, include_json):
952+
@click.option(
953+
"--open",
954+
"open_browser",
955+
is_flag=True,
956+
help="Open the generated index.html in your default browser.",
957+
)
958+
def session(json_file, output, repo, gist, include_json, open_browser):
952959
"""Convert a Claude Code session JSON file to HTML."""
953960
# Determine output directory
954-
if gist and output is None:
961+
if (gist or open_browser) and output is None:
955962
# Extract session ID from JSON file for temp directory name
956963
with open(json_file, "r") as f:
957964
data = json.load(f)
@@ -982,6 +989,10 @@ def session(json_file, output, repo, gist, include_json):
982989
click.echo(f"Preview: {preview_url}")
983990
click.echo(f"Files: {output}")
984991

992+
if open_browser:
993+
index_url = (output / "index.html").resolve().as_uri()
994+
webbrowser.open(index_url)
995+
985996

986997
def resolve_credentials(token, org_uuid):
987998
"""Resolve token and org_uuid from arguments or auto-detect.
@@ -1256,7 +1267,7 @@ def generate_html_from_session_data(session_data, output_dir, github_repo=None):
12561267
"-o",
12571268
"--output",
12581269
type=click.Path(),
1259-
help="Output directory (default: creates folder with session ID, or temp dir with --gist)",
1270+
help="Output directory (default: creates folder with session ID, or temp dir with --gist/--open)",
12601271
)
12611272
@click.option("--token", help="API access token (auto-detected from keychain on macOS)")
12621273
@click.option(
@@ -1277,7 +1288,15 @@ def generate_html_from_session_data(session_data, output_dir, github_repo=None):
12771288
is_flag=True,
12781289
help="Include the JSON session data in the output directory.",
12791290
)
1280-
def import_session(session_id, output, token, org_uuid, repo, gist, include_json):
1291+
@click.option(
1292+
"--open",
1293+
"open_browser",
1294+
is_flag=True,
1295+
help="Open the generated index.html in your default browser.",
1296+
)
1297+
def import_session(
1298+
session_id, output, token, org_uuid, repo, gist, include_json, open_browser
1299+
):
12811300
"""Import a session from the Claude API and convert to HTML.
12821301
12831302
If SESSION_ID is not provided, displays an interactive picker to select a session.
@@ -1337,7 +1356,7 @@ def import_session(session_id, output, token, org_uuid, repo, gist, include_json
13371356
raise click.ClickException(f"Network error: {e}")
13381357

13391358
# Determine output directory
1340-
if gist and output is None:
1359+
if (gist or open_browser) and output is None:
13411360
output = Path(tempfile.gettempdir()) / session_id
13421361
elif output is None:
13431362
output = session_id
@@ -1364,8 +1383,10 @@ def import_session(session_id, output, token, org_uuid, repo, gist, include_json
13641383
click.echo(f"Gist: {gist_url}")
13651384
click.echo(f"Preview: {preview_url}")
13661385
click.echo(f"Files: {output}")
1367-
else:
1368-
click.echo(f"Done! Open {output}/index.html to view.")
1386+
1387+
if open_browser:
1388+
index_url = (output / "index.html").resolve().as_uri()
1389+
webbrowser.open(index_url)
13691390

13701391

13711392
def main():

tests/test_generate_html.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -869,3 +869,79 @@ def mock_run(*args, **kwargs):
869869
assert "Creating GitHub gist" in result.output
870870
assert "gist.github.com" in result.output
871871
assert "gistpreview.github.io" in result.output
872+
873+
874+
class TestOpenOption:
875+
"""Tests for the --open option."""
876+
877+
def test_session_open_calls_webbrowser(self, output_dir, monkeypatch):
878+
"""Test that session --open opens the browser."""
879+
from click.testing import CliRunner
880+
from claude_code_publish import cli
881+
882+
fixture_path = Path(__file__).parent / "sample_session.json"
883+
884+
# Track webbrowser.open calls
885+
opened_urls = []
886+
887+
def mock_open(url):
888+
opened_urls.append(url)
889+
return True
890+
891+
monkeypatch.setattr("claude_code_publish.webbrowser.open", mock_open)
892+
893+
runner = CliRunner()
894+
result = runner.invoke(
895+
cli,
896+
["session", str(fixture_path), "-o", str(output_dir), "--open"],
897+
)
898+
899+
assert result.exit_code == 0
900+
assert len(opened_urls) == 1
901+
assert "index.html" in opened_urls[0]
902+
assert opened_urls[0].startswith("file://")
903+
904+
def test_import_open_calls_webbrowser(self, httpx_mock, output_dir, monkeypatch):
905+
"""Test that import --open opens the browser."""
906+
from click.testing import CliRunner
907+
from claude_code_publish import cli
908+
909+
# Load sample session to mock API response
910+
fixture_path = Path(__file__).parent / "sample_session.json"
911+
with open(fixture_path) as f:
912+
session_data = json.load(f)
913+
914+
httpx_mock.add_response(
915+
url="https://api.anthropic.com/v1/session_ingress/session/test-session-id",
916+
json=session_data,
917+
)
918+
919+
# Track webbrowser.open calls
920+
opened_urls = []
921+
922+
def mock_open(url):
923+
opened_urls.append(url)
924+
return True
925+
926+
monkeypatch.setattr("claude_code_publish.webbrowser.open", mock_open)
927+
928+
runner = CliRunner()
929+
result = runner.invoke(
930+
cli,
931+
[
932+
"import",
933+
"test-session-id",
934+
"--token",
935+
"test-token",
936+
"--org-uuid",
937+
"test-org",
938+
"-o",
939+
str(output_dir),
940+
"--open",
941+
],
942+
)
943+
944+
assert result.exit_code == 0
945+
assert len(opened_urls) == 1
946+
assert "index.html" in opened_urls[0]
947+
assert opened_urls[0].startswith("file://")

0 commit comments

Comments
 (0)