Skip to content

Commit c4e9b0c

Browse files
committed
Restructure CLI commands per feedback
- Rename 'session' command to 'json' for direct file path access - Rename 'import' command to 'web' for web session selection - Update 'list-local' to 'local' with interactive picker and conversion - Remove 'list-web' command (now integrated into 'web' command) - Make 'local' the default command when run with no arguments New CLI structure: - claude-code-publish local - select from local JSONL sessions - claude-code-publish web - select from web sessions - claude-code-publish json - provide direct file path All commands support --gist, --json, --open, and -o options.
1 parent 3d3b42a commit c4e9b0c

File tree

2 files changed

+159
-171
lines changed

2 files changed

+159
-171
lines changed

src/claude_code_publish/__init__.py

Lines changed: 86 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -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")
10751075
def 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-
12791301
def 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

Comments
 (0)