@@ -1070,21 +1070,50 @@ def generate_html(json_path, output_dir, github_repo=None):
10701070 )
10711071
10721072
1073- @click .group (cls = DefaultGroup , default = "list- local" , default_if_no_args = True )
1073+ @click .group (cls = DefaultGroup , default = "local" , default_if_no_args = True )
10741074@click .version_option (None , "-v" , "--version" , package_name = "claude-code-publish" )
10751075def cli ():
10761076 """Convert Claude Code session JSON to mobile-friendly HTML pages."""
10771077 pass
10781078
10791079
1080- @cli .command ("list-local" )
1080+ @cli .command ("local" )
1081+ @click .option (
1082+ "-o" ,
1083+ "--output" ,
1084+ type = click .Path (),
1085+ help = "Output directory (default: temp dir, or '.' with -o .)" ,
1086+ )
1087+ @click .option (
1088+ "--repo" ,
1089+ help = "GitHub repo (owner/name) for commit links. Auto-detected from git push output if not specified." ,
1090+ )
1091+ @click .option (
1092+ "--gist" ,
1093+ is_flag = True ,
1094+ help = "Upload to GitHub Gist and output a gistpreview.github.io URL." ,
1095+ )
1096+ @click .option (
1097+ "--json" ,
1098+ "include_json" ,
1099+ is_flag = True ,
1100+ help = "Include the original JSONL session file in the output directory." ,
1101+ )
1102+ @click .option (
1103+ "--open" ,
1104+ "open_browser" ,
1105+ is_flag = True ,
1106+ help = "Open the generated index.html in your default browser." ,
1107+ )
10811108@click .option (
10821109 "--limit" ,
10831110 default = 10 ,
10841111 help = "Maximum number of sessions to show (default: 10)" ,
10851112)
1086- def list_local (limit ):
1087- """List available local Claude Code sessions."""
1113+ def local_cmd (output , repo , gist , include_json , open_browser , limit ):
1114+ """Select and convert a local Claude Code session to HTML."""
1115+ from datetime import datetime
1116+
10881117 projects_folder = Path .home () / ".claude" / "projects"
10891118
10901119 if not projects_folder .exists ():
@@ -1099,36 +1128,63 @@ def list_local(limit):
10991128 click .echo ("No local sessions found." )
11001129 return
11011130
1102- # Calculate terminal width for formatting
1103- try :
1104- term_width = shutil .get_terminal_size ().columns
1105- except Exception :
1106- term_width = 80
1107-
1108- # Fixed width: date(16) + spaces(2) + size(8) + spaces(2) = 28
1109- fixed_width = 28
1110- summary_width = max (20 , term_width - fixed_width - 1 )
1111-
1112- click .echo ("" )
1113- click .echo ("Recent local sessions:" )
1114- click .echo ("" )
1115-
1116- from datetime import datetime
1117-
1131+ # Build choices for questionary
1132+ choices = []
11181133 for filepath , summary in results :
11191134 stat = filepath .stat ()
11201135 mod_time = datetime .fromtimestamp (stat .st_mtime )
11211136 size_kb = stat .st_size / 1024
11221137 date_str = mod_time .strftime ("%Y-%m-%d %H:%M" )
1138+ # Truncate summary if too long
1139+ if len (summary ) > 50 :
1140+ summary = summary [:47 ] + "..."
1141+ display = f"{ date_str } { size_kb :5.0f} KB { summary } "
1142+ choices .append (questionary .Choice (title = display , value = filepath ))
1143+
1144+ selected = questionary .select (
1145+ "Select a session to convert:" ,
1146+ choices = choices ,
1147+ ).ask ()
1148+
1149+ if selected is None :
1150+ click .echo ("No session selected." )
1151+ return
1152+
1153+ session_file = selected
1154+
1155+ # Determine output directory
1156+ if (gist or open_browser ) and output is None :
1157+ output = Path (tempfile .gettempdir ()) / session_file .stem
1158+ elif output is None :
1159+ output = Path (tempfile .gettempdir ()) / session_file .stem
11231160
1124- # Truncate summary if needed
1125- if len (summary ) > summary_width :
1126- summary = summary [: summary_width - 3 ] + "..."
1161+ output = Path (output )
1162+ generate_html (session_file , output , github_repo = repo )
1163+
1164+ # Copy JSONL file to output directory if requested
1165+ if include_json :
1166+ output .mkdir (exist_ok = True )
1167+ json_dest = output / session_file .name
1168+ shutil .copy (session_file , json_dest )
1169+ json_size_kb = json_dest .stat ().st_size / 1024
1170+ click .echo (f"JSONL: { json_dest } ({ json_size_kb :.1f} KB)" )
1171+
1172+ if gist :
1173+ # Inject gist preview JS and create gist
1174+ inject_gist_preview_js (output )
1175+ click .echo ("Creating GitHub gist..." )
1176+ gist_id , gist_url = create_gist (output )
1177+ preview_url = f"https://gistpreview.github.io/?{ gist_id } /index.html"
1178+ click .echo (f"Gist: { gist_url } " )
1179+ click .echo (f"Preview: { preview_url } " )
1180+ click .echo (f"Files: { output } " )
11271181
1128- click .echo (f"{ date_str } { size_kb :6.0f} KB { summary } " )
1182+ if open_browser :
1183+ index_url = (output / "index.html" ).resolve ().as_uri ()
1184+ webbrowser .open (index_url )
11291185
11301186
1131- @cli .command ()
1187+ @cli .command ("json" )
11321188@click .argument ("json_file" , type = click .Path (exists = True ))
11331189@click .option (
11341190 "-o" ,
@@ -1157,8 +1213,8 @@ def list_local(limit):
11571213 is_flag = True ,
11581214 help = "Open the generated index.html in your default browser." ,
11591215)
1160- def session (json_file , output , repo , gist , include_json , open_browser ):
1161- """Convert a Claude Code session JSON file to HTML."""
1216+ def json_cmd (json_file , output , repo , gist , include_json , open_browser ):
1217+ """Convert a Claude Code session JSON/JSONL file to HTML."""
11621218 # Determine output directory
11631219 if (gist or open_browser ) and output is None :
11641220 # Extract session ID from JSON file for temp directory name
@@ -1242,40 +1298,6 @@ def format_session_for_display(session_data):
12421298 return f"{ session_id } { created_at [:19 ] if created_at else 'N/A' :19} { title } "
12431299
12441300
1245- @cli .command ("list-web" )
1246- @click .option ("--token" , help = "API access token (auto-detected from keychain on macOS)" )
1247- @click .option (
1248- "--org-uuid" , help = "Organization UUID (auto-detected from ~/.claude.json)"
1249- )
1250- def list_web (token , org_uuid ):
1251- """List available sessions from the Claude API."""
1252- try :
1253- token , org_uuid = resolve_credentials (token , org_uuid )
1254- except click .ClickException :
1255- raise
1256-
1257- try :
1258- sessions_data = fetch_sessions (token , org_uuid )
1259- except httpx .HTTPStatusError as e :
1260- raise click .ClickException (
1261- f"API request failed: { e .response .status_code } { e .response .text } "
1262- )
1263- except httpx .RequestError as e :
1264- raise click .ClickException (f"Network error: { e } " )
1265-
1266- sessions = sessions_data .get ("data" , [])
1267- if not sessions :
1268- click .echo ("No sessions found." )
1269- return
1270-
1271- # Print header
1272- click .echo (f"{ 'Session ID' :<35} { 'Created' :<19} Name" )
1273- click .echo ("-" * 80 )
1274-
1275- for session_data in sessions :
1276- click .echo (format_session_for_display (session_data ))
1277-
1278-
12791301def generate_html_from_session_data (session_data , output_dir , github_repo = None ):
12801302 """Generate HTML from session data dict (instead of file path)."""
12811303 output_dir = Path (output_dir )
@@ -1463,7 +1485,7 @@ def generate_html_from_session_data(session_data, output_dir, github_repo=None):
14631485 )
14641486
14651487
1466- @cli .command ("import " )
1488+ @cli .command ("web " )
14671489@click .argument ("session_id" , required = False )
14681490@click .option (
14691491 "-o" ,
@@ -1496,10 +1518,10 @@ def generate_html_from_session_data(session_data, output_dir, github_repo=None):
14961518 is_flag = True ,
14971519 help = "Open the generated index.html in your default browser." ,
14981520)
1499- def import_session (
1521+ def web_cmd (
15001522 session_id , output , token , org_uuid , repo , gist , include_json , open_browser
15011523):
1502- """Import a session from the Claude API and convert to HTML.
1524+ """Select and convert a web session from the Claude API to HTML.
15031525
15041526 If SESSION_ID is not provided, displays an interactive picker to select a session.
15051527 """
0 commit comments