{"slug": "claude-code-wrapper-with-session-naming-and-auto-summarization", "title": "Claude Code wrapper with session naming and auto-summarization", "summary": "A developer created a Bash wrapper for Claude Code that adds session naming, listing, and management capabilities to the AI coding tool. The wrapper, named `cc`, supports named sessions, session resumption by name or index, and includes an opt-in Gemini-based auto-summarization feature for previous sessions when listing. The tool also implements safer destructive operations by using `trash` instead of `rm` and requires explicit confirmation with session count display for clearing all sessions.", "body_md": "| #!/usr/bin/env bash | |\n| # cc - Claude Code wrapper with session naming | |\n| # Adapted from https://gist.github.com/zinknovo/cf21268e90419e9c0e93a9bd448591a4 | |\n| # | |\n| # Local changes: | |\n| # - Config dir defaults to ~/.claude-2 (override via $CLAUDE_CONFIG_DIR) | |\n| # - Destructive ops use `trash` instead of `rm` (per CLAUDE.md global rules) | |\n| # - `clear` requires typing \"yes\" + shows session count | |\n| # - Gemini auto-summary is opt-in via CC_AUTO_SUMMARY=1 | |\n| # - Removed _check_version (depended on external check-cli-compat) | |\n| # | |\n| # Usage: | |\n| # cc Start a new Claude Code session | |\n| # cc n <name> Start a new session with a name | |\n| # cc ls List sessions (current project) | |\n| # cc lsa List sessions across all projects | |\n| # cc r <name|index> Resume a session by name or index | |\n| # cc rn <index> <name> Rename a session | |\n| # cc rm <name|index> Delete (trash) a session | |\n| # cc clear Delete (trash) all sessions | |\n| # | |\n| # Env: | |\n| # CLAUDE_CONFIG_DIR Default: $HOME/.claude-2 | |\n| # CC_AUTO_SUMMARY=1 Enable Gemini-based prev-session auto-summary on `ls` | |\n| # CLAUDE_CMD Underlying CLI command (default: claude) | |\n| # CMD_NAME Display name in help text (default: basename $0) | |\n| set -euo pipefail | |\n| CLAUDE_CMD=\"${CLAUDE_CMD:-$HOME/.local/bin/claude}\" | |\n| CMD_NAME=\"${CMD_NAME:-$(basename \"$0\")}\" | |\n| CLAUDE_DIR=\"${CLAUDE_CONFIG_DIR:-$HOME/.claude-2}\" | |\n| export CLAUDE_CONFIG_DIR=\"$CLAUDE_DIR\" | |\n| # Mirror the user's `claude` alias: write a profile marker so other tools | |\n| # can detect which profile (claude-1 / claude-2) is active in this tty. | |\n| _write_profile_marker() { | |\n| local profile=\"${CLAUDE_DIR##*/.}\" # ~/.claude-2 -> claude-2 | |\n| local tty_id | |\n| tty_id=$({ tty 2>/dev/null || true; } | tr '/' '_') | |\n| [ -n \"$tty_id\" ] && [ \"$tty_id\" != \"not a tty\" ] && echo \"$profile\" > \"/tmp/.claude_profile_$tty_id\" 2>/dev/null || true | |\n| } | |\n| _write_profile_marker | |\n| NAMES_DIR=\"$CLAUDE_DIR/session-names\" | |\n| PROJECTS_DIR=\"$CLAUDE_DIR/projects\" | |\n| SUMMARIES_DIR=\"$CLAUDE_DIR/session-summaries\" | |\n| mkdir -p \"$NAMES_DIR\" \"$SUMMARIES_DIR\" | |\n| # Encode a path to Claude Code's project directory name | |\n| # /Users/Z1nk -> -Users-Z1nk | |\n| encode_project_path() { | |\n| echo \"$1\" | sed -e 's|/|-|g' -e 's| |-|g' | |\n| } | |\n| current_project_id() { | |\n| encode_project_path \"$PWD\" | |\n| } | |\n| names_file() { | |\n| local project_id=\"${1:-default}\" | |\n| echo \"$NAMES_DIR/$project_id.json\" | |\n| } | |\n| get_name() { | |\n| local project_id=\"$1\" session_id=\"$2\" | |\n| local nf | |\n| nf=$(names_file \"$project_id\") | |\n| [ -f \"$nf\" ] && python3 -c \" | |\n| import json, sys | |\n| d = json.load(open(sys.argv[1])) | |\n| print(d.get(sys.argv[2], '')) | |\n| \" \"$nf\" \"$session_id\" 2>/dev/null || echo \"\" | |\n| } | |\n| set_name() { | |\n| local project_id=\"$1\" session_id=\"$2\" name=\"$3\" | |\n| local nf | |\n| nf=$(names_file \"$project_id\") | |\n| python3 -c \" | |\n| import json, os, sys | |\n| nf, sid, name = sys.argv[1], sys.argv[2], sys.argv[3] | |\n| d = {} | |\n| if os.path.exists(nf): | |\n| d = json.load(open(nf)) | |\n| d[sid] = name | |\n| json.dump(d, open(nf, 'w'), indent=2, ensure_ascii=False) | |\n| \" \"$nf\" \"$session_id\" \"$name\" | |\n| } | |\n| rm_name() { | |\n| local project_id=\"$1\" session_id=\"$2\" | |\n| local nf | |\n| nf=$(names_file \"$project_id\") | |\n| [ -f \"$nf\" ] && python3 -c \" | |\n| import json, os, sys | |\n| nf, sid = sys.argv[1], sys.argv[2] | |\n| d = json.load(open(nf)) | |\n| d.pop(sid, None) | |\n| json.dump(d, open(nf, 'w'), indent=2, ensure_ascii=False) | |\n| \" \"$nf\" \"$session_id\" 2>/dev/null | |\n| } | |\n| # Opt-in: generate Gemini summary for previous session in background | |\n| generate_summary_for_latest() { | |\n| [ \"${CC_AUTO_SUMMARY:-0}\" = \"1\" ] || return 0 | |\n| command -v gemini >/dev/null 2>&1 || return 0 | |\n| python3 - \"$PWD\" \"$PROJECTS_DIR\" \"$SUMMARIES_DIR\" <<'PYEOF' & | |\n| import json, os, sys, glob, subprocess | |\n| cwd, projects_dir, summaries_dir = sys.argv[1], sys.argv[2], sys.argv[3] | |\n| project_id = cwd.replace(\"/\", \"-\").replace(\" \", \"-\") | |\n| project_dir = os.path.join(projects_dir, project_id) | |\n| if not os.path.isdir(project_dir): | |\n| sys.exit(0) | |\n| sessions = [] | |\n| for f in glob.glob(os.path.join(project_dir, \"*.jsonl\")): | |\n| sid = os.path.basename(f).replace(\".jsonl\", \"\") | |\n| sessions.append((sid, f, os.path.getmtime(f))) | |\n| sessions.sort(key=lambda x: x[2], reverse=True) | |\n| for sid, fpath, _ in sessions[1:2]: | |\n| summary_file = os.path.join(summaries_dir, f\"{sid}.txt\") | |\n| if os.path.exists(summary_file): | |\n| continue | |\n| user_msgs = [] | |\n| try: | |\n| with open(fpath) as fh: | |\n| for line in fh: | |\n| line = line.strip() | |\n| if not line: | |\n| continue | |\n| try: | |\n| record = json.loads(line) | |\n| if record.get(\"type\") != \"user\": | |\n| continue | |\n| msg = record.get(\"message\", {}) | |\n| content = msg.get(\"content\", \"\") | |\n| text = \"\" | |\n| if isinstance(content, str): | |\n| text = content.strip() | |\n| elif isinstance(content, list): | |\n| for c in content: | |\n| if isinstance(c, dict) and c.get(\"type\") == \"text\": | |\n| t = c.get(\"text\", \"\").strip() | |\n| if t: | |\n| text = t | |\n| break | |\n| if text: | |\n| user_msgs.append(text[:500]) | |\n| except Exception: | |\n| continue | |\n| except Exception: | |\n| continue | |\n| if len(user_msgs) < 2: | |\n| continue | |\n| selected = user_msgs[:5] + user_msgs[-5:] if len(user_msgs) > 10 else user_msgs | |\n| prompt_text = \"Summarize this conversation in one short sentence (max 50 chars, same language as the conversation). Just output the summary, nothing else.\\n\\n\" | |\n| for i, m in enumerate(selected, 1): | |\n| prompt_text += f\"User message {i}: {m}\\n\\n\" | |\n| try: | |\n| result = subprocess.run( | |\n| [\"gemini\", \"-p\", prompt_text], | |\n| capture_output=True, text=True, timeout=15 | |\n| ) | |\n| summary = result.stdout.strip().replace(\"\\n\", \" \")[:80] | |\n| if summary and \"error\" not in summary.lower()[:20]: | |\n| with open(summary_file, \"w\") as sf: | |\n| sf.write(summary) | |\n| except Exception: | |\n| pass | |\n| PYEOF | |\n| } | |\n| cmd_list() { | |\n| local all_projects=false | |\n| [ \"${1:-}\" = \"-a\" ] && all_projects=true | |\n| python3 - \"$all_projects\" \"$PWD\" \"$PROJECTS_DIR\" \"$NAMES_DIR\" \"$SUMMARIES_DIR\" <<'PYEOF' | |\n| import json, os, sys, glob | |\n| from datetime import datetime, timezone | |\n| all_projects = sys.argv[1] == \"true\" | |\n| cwd = sys.argv[2] | |\n| projects_dir = sys.argv[3] | |\n| names_dir = sys.argv[4] | |\n| summaries_dir = sys.argv[5] | |\n| home = os.path.expanduser(\"~\") | |\n| desktop_sessions_dir = os.path.join(home, \"Library\", \"Application Support\", \"Claude\", \"claude-code-sessions\") | |\n| def encode_path(p): | |\n| return p.replace(\"/\", \"-\").replace(\" \", \"-\") | |\n| def decode_path(encoded): | |\n| if not encoded.startswith(\"-\"): | |\n| return encoded | |\n| parts = encoded[1:].split(\"-\") | |\n| result = \"\" | |\n| i = 0 | |\n| while i < len(parts): | |\n| for j in range(len(parts), i, -1): | |\n| candidate = result + \"/\" + \"-\".join(parts[i:j]) | |\n| if os.path.isdir(candidate) or j == i + 1: | |\n| result = candidate | |\n| i = j | |\n| break | |\n| return result if result else \"/\" + encoded[1:].replace(\"-\", \"/\") | |\n| def get_display_path(encoded): | |\n| decoded = decode_path(encoded) | |\n| if decoded.startswith(home): | |\n| return \"~\" + decoded[len(home):] | |\n| elif decoded == \"/\": | |\n| return \"/\" | |\n| return decoded | |\n| def load_desktop_titles(): | |\n| titles = {} | |\n| if not os.path.isdir(desktop_sessions_dir): | |\n| return titles | |\n| for f in glob.glob(os.path.join(desktop_sessions_dir, \"*\", \"*\", \"*.json\")): | |\n| try: | |\n| d = json.load(open(f)) | |\n| cli_sid = d.get(\"cliSessionId\", \"\") | |\n| title = d.get(\"title\", \"\") | |\n| if cli_sid and title: | |\n| titles[cli_sid] = title | |\n| except Exception: | |\n| continue | |\n| return titles | |\n| desktop_titles = load_desktop_titles() | |\n| current_project = encode_path(cwd) | |\n| sessions = [] | |\n| for project_dir in sorted(glob.glob(os.path.join(projects_dir, \"*\"))): | |\n| if not os.path.isdir(project_dir): | |\n| continue | |\n| project_id = os.path.basename(project_dir) | |\n| if not all_projects and project_id != current_project: | |\n| continue | |\n| nf = os.path.join(names_dir, f\"{project_id}.json\") | |\n| names = {} | |\n| if os.path.exists(nf): | |\n| try: | |\n| names = json.load(open(nf)) | |\n| except Exception: | |\n| pass | |\n| display_path = get_display_path(project_id) | |\n| for f in glob.glob(os.path.join(project_dir, \"*.jsonl\")): | |\n| fname = os.path.basename(f) | |\n| sid = fname.replace(\".jsonl\", \"\") | |\n| msg_count = 0 | |\n| last_user_msg = \"\" | |\n| last_time = \"\" | |\n| try: | |\n| with open(f) as fh: | |\n| for line in fh: | |\n| line = line.strip() | |\n| if not line: | |\n| continue | |\n| try: | |\n| record = json.loads(line) | |\n| rtype = record.get(\"type\", \"\") | |\n| ts = record.get(\"timestamp\", \"\") | |\n| if ts: | |\n| last_time = ts | |\n| if rtype == \"user\": | |\n| msg_count += 1 | |\n| msg = record.get(\"message\", {}) | |\n| content = msg.get(\"content\", \"\") | |\n| text = \"\" | |\n| if isinstance(content, str): | |\n| text = content.replace(\"\\n\", \" \").strip() | |\n| elif isinstance(content, list): | |\n| for c in content: | |\n| if isinstance(c, dict) and c.get(\"type\") == \"text\": | |\n| text = c.get(\"text\", \"\").replace(\"\\n\", \" \").strip() | |\n| if text: | |\n| break | |\n| if text: | |\n| last_user_msg = text[:50] | |\n| elif rtype == \"assistant\": | |\n| msg_count += 1 | |\n| except Exception: | |\n| continue | |\n| except Exception: | |\n| continue | |\n| if msg_count == 0: | |\n| continue | |\n| custom_name = desktop_titles.get(sid, \"\") or names.get(sid, \"\") | |\n| summary = \"\" | |\n| sf = os.path.join(summaries_dir, f\"{sid}.txt\") | |\n| if os.path.exists(sf): | |\n| try: | |\n| summary = open(sf).read().strip()[:80] | |\n| except Exception: | |\n| pass | |\n| time_str = \"\" | |\n| if last_time: | |\n| try: | |\n| if isinstance(last_time, (int, float)): | |\n| dt = datetime.fromtimestamp(last_time / 1000, tz=timezone.utc) | |\n| else: | |\n| dt = datetime.fromisoformat(str(last_time).replace(\"Z\", \"+00:00\")) | |\n| now = datetime.now(timezone.utc) | |\n| diff = now - dt | |\n| if diff.days > 0: | |\n| time_str = f\"{diff.days}d ago\" | |\n| elif diff.seconds > 3600: | |\n| time_str = f\"{diff.seconds // 3600}h ago\" | |\n| else: | |\n| time_str = f\"{diff.seconds // 60}m ago\" | |\n| except Exception: | |\n| time_str = str(last_time)[:16] | |\n| sessions.append({ | |\n| \"project\": project_id, | |\n| \"project_path\": display_path, | |\n| \"sid\": sid, | |\n| \"name\": custom_name, | |\n| \"summary\": summary, | |\n| \"last_msg\": last_user_msg, | |\n| \"time\": time_str, | |\n| \"sort_time\": last_time, | |\n| \"msgs\": msg_count, | |\n| }) | |\n| sessions.sort(key=lambda s: s[\"sort_time\"] if s[\"sort_time\"] else \"\", reverse=True) | |\n| if not sessions: | |\n| print(\" No sessions found.\") | |\n| sys.exit(0) | |\n| current_proj = \"\" | |\n| for i, s in enumerate(sessions, 1): | |\n| if all_projects and s[\"project\"] != current_proj: | |\n| current_proj = s[\"project\"] | |\n| print(f\" \\033[0;33m{s['project_path']}\\033[0m\") | |\n| proj = f\"\\033[0;33m{s['project_path']}\\033[0m\" if not all_projects else \"\" | |\n| preview = s[\"summary\"] or s[\"last_msg\"] or \"(empty)\" | |\n| line = f\" {i:>3}. {s['time']:>8} {s['msgs']:>3} msgs\" | |\n| if not all_projects: | |\n| line += f\" {proj}\" | |\n| if s[\"name\"]: | |\n| line += f\" \\033[1;36m{s['name']}\\033[0m\" | |\n| if s[\"summary\"]: | |\n| line += f\" ({s['summary'][:40]})\" | |\n| else: | |\n| line += f\" {preview}\" | |\n| print(line) | |\n| PYEOF | |\n| } | |\n| resolve_session() { | |\n| local query=\"$1\" | |\n| local all_flag=\"${2:-}\" | |\n| python3 - \"$query\" \"$PWD\" \"$all_flag\" \"$PROJECTS_DIR\" \"$NAMES_DIR\" <<'PYEOF' | head -1 | |\n| import json, os, sys, glob | |\n| query = sys.argv[1] | |\n| cwd = sys.argv[2] | |\n| all_flag = sys.argv[3] | |\n| projects_dir = sys.argv[4] | |\n| names_dir = sys.argv[5] | |\n| def encode_path(p): | |\n| return p.replace(\"/\", \"-\").replace(\" \", \"-\") | |\n| current_project = encode_path(cwd) | |\n| sessions = [] | |\n| for project_dir in sorted(glob.glob(os.path.join(projects_dir, \"*\"))): | |\n| if not os.path.isdir(project_dir): | |\n| continue | |\n| project_id = os.path.basename(project_dir) | |\n| if all_flag != \"-a\" and project_id != current_project: | |\n| continue | |\n| nf = os.path.join(names_dir, f\"{project_id}.json\") | |\n| names = {} | |\n| if os.path.exists(nf): | |\n| try: | |\n| names = json.load(open(nf)) | |\n| except Exception: | |\n| pass | |\n| for f in glob.glob(os.path.join(project_dir, \"*.jsonl\")): | |\n| sid = os.path.basename(f).replace(\".jsonl\", \"\") | |\n| msg_count = 0 | |\n| last_time = \"\" | |\n| try: | |\n| with open(f) as fh: | |\n| for line in fh: | |\n| line = line.strip() | |\n| if not line: | |\n| continue | |\n| try: | |\n| r = json.loads(line) | |\n| if r.get(\"type\") in (\"user\", \"assistant\"): | |\n| msg_count += 1 | |\n| ts = r.get(\"timestamp\", \"\") | |\n| if ts: | |\n| last_time = ts | |\n| except Exception: | |\n| continue | |\n| except Exception: | |\n| continue | |\n| if msg_count == 0: | |\n| continue | |\n| custom_name = names.get(sid, \"\") | |\n| sessions.append({\"sid\": sid, \"name\": custom_name, \"project\": project_id, \"sort_time\": last_time}) | |\n| sessions.sort(key=lambda s: s[\"sort_time\"] if s[\"sort_time\"] else \"\", reverse=True) | |\n| try: | |\n| idx = int(query) - 1 | |\n| if 0 <= idx < len(sessions): | |\n| print(sessions[idx][\"sid\"]) | |\n| sys.exit(0) | |\n| except ValueError: | |\n| pass | |\n| # Match by session ID (full UUID or unambiguous prefix, min 4 chars) | |\n| if len(query) >= 4 and all(c in \"0123456789abcdef-\" for c in query.lower()): | |\n| q = query.lower() | |\n| exact = [s for s in sessions if s[\"sid\"].lower() == q] | |\n| if exact: | |\n| print(exact[0][\"sid\"]) | |\n| sys.exit(0) | |\n| prefix = [s for s in sessions if s[\"sid\"].lower().startswith(q)] | |\n| if len(prefix) == 1: | |\n| print(prefix[0][\"sid\"]) | |\n| sys.exit(0) | |\n| if len(prefix) > 1: | |\n| print(f\"ERROR: session id prefix '{query}' is ambiguous ({len(prefix)} matches)\", file=sys.stderr) | |\n| sys.exit(1) | |\n| for s in sessions: | |\n| if s[\"name\"] == query: | |\n| print(s[\"sid\"]) | |\n| sys.exit(0) | |\n| for s in sessions: | |\n| if s[\"name\"] and query.lower() in s[\"name\"].lower(): | |\n| print(s[\"sid\"]) | |\n| sys.exit(0) | |\n| print(f\"ERROR: No session found matching '{query}'\", file=sys.stderr) | |\n| sys.exit(1) | |\n| PYEOF | |\n| } | |\n| resolve_project_for_session() { | |\n| local session_id=\"$1\" | |\n| python3 -c \" | |\n| import os, glob, sys | |\n| sid = sys.argv[1] | |\n| projects_dir = sys.argv[2] | |\n| for f in glob.glob(os.path.join(projects_dir, '*', sid + '.jsonl')): | |\n| print(os.path.basename(os.path.dirname(f))) | |\n| break | |\n| \" \"$session_id\" \"$PROJECTS_DIR\" 2>/dev/null | |\n| } | |\n| find_newest_session_after() { | |\n| local ts=\"$1\" | |\n| python3 - \"$ts\" \"$PWD\" \"$PROJECTS_DIR\" <<'PYEOF' | head -1 | |\n| import os, sys, glob | |\n| ts = int(sys.argv[1]) | |\n| cwd = sys.argv[2] | |\n| projects_dir = sys.argv[3] | |\n| project_id = cwd.replace(\"/\", \"-\").replace(\" \", \"-\") | |\n| project_dir = os.path.join(projects_dir, project_id) | |\n| newest = None | |\n| newest_time = ts | |\n| if os.path.isdir(project_dir): | |\n| for f in glob.glob(os.path.join(project_dir, \"*.jsonl\")): | |\n| try: | |\n| mtime = int(os.path.getmtime(f) * 1000) | |\n| if mtime > ts and mtime > newest_time: | |\n| newest = os.path.basename(f).replace(\".jsonl\", \"\") | |\n| newest_time = mtime | |\n| except Exception: | |\n| continue | |\n| if newest: | |\n| print(newest) | |\n| PYEOF | |\n| } | |\n| launch_and_name() { | |\n| local session_name=\"$1\" | |\n| shift | |\n| local ts | |\n| ts=$(python3 -c \"import time; print(int(time.time() * 1000))\") | |\n| \"$CLAUDE_CMD\" \"$@\" | |\n| local sid | |\n| sid=$(find_newest_session_after \"$ts\") | |\n| if [ -n \"$sid\" ] && [ -n \"$session_name\" ]; then | |\n| local project_id | |\n| project_id=$(resolve_project_for_session \"$sid\") | |\n| if [ -n \"$project_id\" ]; then | |\n| set_name \"$project_id\" \"$sid\" \"$session_name\" | |\n| echo \"Session saved as: $session_name\" | |\n| fi | |\n| fi | |\n| } | |\n| case \"${1:-}\" in | |\n| ls) | |\n| generate_summary_for_latest | |\n| cmd_list \"\" | |\n| ;; | |\n| lsa) | |\n| generate_summary_for_latest | |\n| cmd_list \"-a\" | |\n| ;; | |\n| r|resume) | |\n| [ -z \"${2:-}\" ] && echo \"Usage: $CMD_NAME r <name|index>\" && exit 1 | |\n| # Numeric index resolves in current-project scope (matches `cc ls`); | |\n| # names/IDs resolve cross-project (matches `cc lsa`). | |\n| if [[ \"$2\" =~ ^[0-9]+$ ]]; then | |\n| sid=$(resolve_session \"$2\" \"\") | |\n| else | |\n| sid=$(resolve_session \"$2\" \"-a\") | |\n| fi | |\n| if [ -n \"$sid\" ]; then | |\n| exec \"$CLAUDE_CMD\" --resume \"$sid\" | |\n| fi | |\n| ;; | |\n| rn|rename) | |\n| { [ -z \"${2:-}\" ] || [ -z \"${3:-}\" ]; } && echo \"Usage: $CMD_NAME rn <index> <name>\" && exit 1 | |\n| target=\"$2\" | |\n| shift 2 | |\n| sid=$(resolve_session \"$target\" \"-a\") | |\n| if [ -n \"$sid\" ]; then | |\n| project_id=$(resolve_project_for_session \"$sid\") | |\n| set_name \"$project_id\" \"$sid\" \"$*\" | |\n| echo \"Named session as: $*\" | |\n| fi | |\n| ;; | |\n| rm|delete) | |\n| [ -z \"${2:-}\" ] && echo \"Usage: $CMD_NAME rm <name|index|session-id>\" && exit 1 | |\n| sid=$(resolve_session \"$2\" \"-a\" 2>/dev/null) | |\n| # Fallback: treat arg as a raw session id and locate the file on disk | |\n| if [ -z \"$sid\" ] && [[ \"$2\" =~ ^[0-9a-fA-F-]{4,}$ ]]; then | |\n| match=$(find \"$PROJECTS_DIR\" -maxdepth 2 -name \"$2*.jsonl\" 2>/dev/null) | |\n| count=$(printf \"%s\\n\" \"$match\" | grep -c .) | |\n| if [ \"$count\" = \"1\" ]; then | |\n| sid=$(basename \"$match\" .jsonl) | |\n| elif [ \"$count\" -gt 1 ]; then | |\n| echo \"ERROR: session id '$2' matches $count files\" >&2 | |\n| exit 1 | |\n| fi | |\n| fi | |\n| if [ -n \"$sid\" ]; then | |\n| project_id=$(resolve_project_for_session \"$sid\") | |\n| session_file=\"$PROJECTS_DIR/$project_id/$sid.jsonl\" | |\n| session_dir=\"$PROJECTS_DIR/$project_id/$sid\" | |\n| [ -f \"$session_file\" ] && trash \"$session_file\" | |\n| [ -d \"$session_dir\" ] && trash \"$session_dir\" | |\n| [ -n \"$project_id\" ] && rm_name \"$project_id\" \"$sid\" | |\n| echo \"Trashed session: $sid\" | |\n| else | |\n| echo \"ERROR: No session found matching '$2'\" >&2 | |\n| exit 1 | |\n| fi | |\n| ;; | |\n| clear) | |\n| count=$(find \"$PROJECTS_DIR\" -name \"*.jsonl\" 2>/dev/null | wc -l | tr -d ' ') | |\n| echo \"This will move $count session file(s) and all name maps to the Trash.\" | |\n| echo -n \"Type 'yes' to confirm: \" | |\n| read -r confirm | |\n| if [ \"$confirm\" = \"yes\" ]; then | |\n| # Collect files into an array so trash gets explicit paths | |\n| while IFS= read -r f; do | |\n| [ -n \"$f\" ] && trash \"$f\" | |\n| done < <(find \"$PROJECTS_DIR\" -name \"*.jsonl\" 2>/dev/null) | |\n| for nf in \"$NAMES_DIR\"/*.json; do | |\n| [ -f \"$nf\" ] && trash \"$nf\" | |\n| done | |\n| echo \"All sessions trashed.\" | |\n| else | |\n| echo \"Cancelled.\" | |\n| fi | |\n| ;; | |\n| n|new) | |\n| [ -z \"${2:-}\" ] && echo \"Usage: $CMD_NAME n <name>\" && exit 1 | |\n| shift | |\n| launch_and_name \"$*\" | |\n| ;; | |\n| help|-h|--help) | |\n| cat <<EOF | |\n| $CMD_NAME - Claude Code wrapper with session naming | |\n| Usage: | |\n| $CMD_NAME n <name> Start a new session with a name | |\n| $CMD_NAME ls List sessions (current project) | |\n| $CMD_NAME lsa List sessions across all projects | |\n| $CMD_NAME r <name|index> Resume session (numeric index = current project, like 'ls'; | |\n| name/UUID = cross-project, like 'lsa') | |\n| $CMD_NAME rn <index> <name> Rename a session | |\n| $CMD_NAME rm <name|index|id> Trash a session (by name, list index, or session UUID/prefix) | |\n| $CMD_NAME clear Trash all sessions (with confirmation) | |\n| $CMD_NAME help Show this help | |\n| Note: sessions are scoped per profile via CLAUDE_CONFIG_DIR. Two profiles | |\n| (e.g. ~/.claude and ~/.claude-2) have independent session lists — switching | |\n| profile changes what 'ls' and 'r <index>' see. | |\n| Env: | |\n| CLAUDE_CONFIG_DIR=$CLAUDE_DIR | |\n| CC_AUTO_SUMMARY=${CC_AUTO_SUMMARY:-0} (set to 1 to enable Gemini summaries) | |\n| EOF | |\n| ;; | |\n| \"\") | |\n| # No args: behave like a plain `claude` launcher (matches former alias) | |\n| exec \"$CLAUDE_CMD\" | |\n| ;; | |\n| *) | |\n| echo \"Unknown command: $1 (try '$CMD_NAME help')\" && exit 1 | |\n| ;; | |\n| esac |", "url": "https://wpnews.pro/news/claude-code-wrapper-with-session-naming-and-auto-summarization", "canonical_source": "https://gist.github.com/renaudjx/2b5852276eaee16b00aaa4746810dbb9", "published_at": "2026-05-16 15:13:56+00:00", "updated_at": "2026-05-30 08:12:52.486227+00:00", "lang": "en", "topics": ["ai-tools", "large-language-models", "generative-ai"], "entities": ["Claude Code", "Gemini", "Claude"], "alternates": {"html": "https://wpnews.pro/news/claude-code-wrapper-with-session-naming-and-auto-summarization", "markdown": "https://wpnews.pro/news/claude-code-wrapper-with-session-naming-and-auto-summarization.md", "text": "https://wpnews.pro/news/claude-code-wrapper-with-session-naming-and-auto-summarization.txt", "jsonld": "https://wpnews.pro/news/claude-code-wrapper-with-session-naming-and-auto-summarization.jsonld"}}