@@ -158,6 +158,20 @@ def _get_jsonl_summary(filepath, max_length=200):
158158 return "(no summary)"
159159
160160
161+ def _should_include_session (filepath , include_agents = False ):
162+ """Check if a session file should be included in listings.
163+
164+ Returns (True, summary) if the session should be included,
165+ (False, None) if it should be skipped.
166+ """
167+ if not include_agents and filepath .name .startswith ("agent-" ):
168+ return False , None
169+ summary = get_session_summary (filepath )
170+ if summary .lower () == "warmup" or summary == "(no summary)" :
171+ return False , None
172+ return True , summary
173+
174+
161175def find_local_sessions (folder , limit = 10 ):
162176 """Find recent JSONL session files in the given folder.
163177
@@ -170,11 +184,8 @@ def find_local_sessions(folder, limit=10):
170184
171185 results = []
172186 for f in folder .glob ("**/*.jsonl" ):
173- if f .name .startswith ("agent-" ):
174- continue
175- summary = get_session_summary (f )
176- # Skip boring/empty sessions
177- if summary .lower () == "warmup" or summary == "(no summary)" :
187+ include , summary = _should_include_session (f )
188+ if not include :
178189 continue
179190 results .append ((f , summary ))
180191
@@ -183,6 +194,53 @@ def find_local_sessions(folder, limit=10):
183194 return results [:limit ]
184195
185196
197+ def find_sessions_for_project (projects_folder , project_path , limit = 10 ):
198+ """Find sessions for a specific project directory.
199+
200+ Returns (sessions_list, project_folder_exists) where:
201+ - sessions_list: list of (Path, summary) tuples sorted by modification time
202+ - project_folder_exists: True if the project folder exists, False otherwise
203+ """
204+ projects_folder = Path (projects_folder )
205+ encoded = encode_path_to_folder_name (project_path )
206+ project_folder = projects_folder / encoded
207+
208+ if not project_folder .exists ():
209+ return [], False
210+
211+ results = []
212+ for f in project_folder .glob ("*.jsonl" ): # Not recursive - just this project
213+ include , summary = _should_include_session (f )
214+ if not include :
215+ continue
216+ results .append ((f , summary ))
217+
218+ results .sort (key = lambda x : x [0 ].stat ().st_mtime , reverse = True )
219+ return results [:limit ], True
220+
221+
222+ def find_sessions_excluding_project (projects_folder , exclude_project_path , limit = 10 ):
223+ """Find recent sessions from all projects except the specified one.
224+
225+ Returns a list of (Path, summary) tuples sorted by modification time.
226+ """
227+ projects_folder = Path (projects_folder )
228+ exclude_encoded = encode_path_to_folder_name (exclude_project_path )
229+
230+ results = []
231+ for f in projects_folder .glob ("**/*.jsonl" ):
232+ # Skip if in the excluded project
233+ if f .parent .name == exclude_encoded :
234+ continue
235+ include , summary = _should_include_session (f )
236+ if not include :
237+ continue
238+ results .append ((f , summary ))
239+
240+ results .sort (key = lambda x : x [0 ].stat ().st_mtime , reverse = True )
241+ return results [:limit ]
242+
243+
186244def get_project_display_name (folder_name ):
187245 """Convert encoded folder name to readable project name.
188246
@@ -242,6 +300,29 @@ def get_project_display_name(folder_name):
242300 return folder_name
243301
244302
303+ def encode_path_to_folder_name (path ):
304+ """Encode a filesystem path to Claude's project folder naming convention.
305+
306+ This is the inverse of how Claude Code stores project folders:
307+ - /Users/foo/bar -> -Users-foo-bar
308+ - /Users/foo/.hidden -> -Users-foo--hidden
309+
310+ Note: This is NOT the inverse of get_project_display_name(), which is
311+ a one-way beautifier that extracts meaningful project names for display.
312+ """
313+ # Convert to absolute path but don't resolve symlinks
314+ # (resolve() doesn't work well with non-existent paths)
315+ p = Path (path )
316+ if not p .is_absolute ():
317+ p = Path .cwd () / p
318+ path = str (p )
319+ # Hidden directories: /. becomes --
320+ encoded = path .replace ("/." , "--" )
321+ # All other slashes become dashes
322+ encoded = encoded .replace ("/" , "-" )
323+ return encoded
324+
325+
245326def find_all_sessions (folder , include_agents = False ):
246327 """Find all sessions in a Claude projects folder, grouped by project.
247328
@@ -260,13 +341,8 @@ def find_all_sessions(folder, include_agents=False):
260341 projects = {}
261342
262343 for session_file in folder .glob ("**/*.jsonl" ):
263- # Skip agent files unless requested
264- if not include_agents and session_file .name .startswith ("agent-" ):
265- continue
266-
267- # Get summary and skip boring sessions
268- summary = get_session_summary (session_file )
269- if summary .lower () == "warmup" or summary == "(no summary)" :
344+ include , summary = _should_include_session (session_file , include_agents )
345+ if not include :
270346 continue
271347
272348 # Get project folder
@@ -1432,15 +1508,21 @@ def local_cmd(output, output_auto, repo, gist, include_json, open_browser, limit
14321508 return
14331509
14341510 click .echo ("Loading local sessions..." )
1435- results = find_local_sessions ( projects_folder , limit = limit )
1511+ cwd = os . getcwd ( )
14361512
1437- if not results :
1438- click .echo ("No local sessions found." )
1439- return
1513+ # Get sessions for current project
1514+ current_sessions , project_exists = find_sessions_for_project (
1515+ projects_folder , cwd , limit = limit
1516+ )
1517+
1518+ # Get sessions from other projects
1519+ other_sessions = find_sessions_excluding_project (projects_folder , cwd , limit = limit )
14401520
1441- # Build choices for questionary
1521+ # Build choices for questionary with section headers
14421522 choices = []
1443- for filepath , summary in results :
1523+
1524+ # Helper function to format session display
1525+ def format_session (filepath , summary , include_project = False ):
14441526 stat = filepath .stat ()
14451527 mod_time = datetime .fromtimestamp (stat .st_mtime )
14461528 size_kb = stat .st_size / 1024
@@ -1449,7 +1531,34 @@ def local_cmd(output, output_auto, repo, gist, include_json, open_browser, limit
14491531 if len (summary ) > 50 :
14501532 summary = summary [:47 ] + "..."
14511533 display = f"{ date_str } { size_kb :5.0f} KB { summary } "
1452- choices .append (questionary .Choice (title = display , value = filepath ))
1534+ if include_project :
1535+ project_name = get_project_display_name (filepath .parent .name )
1536+ display = f"{ display } [{ project_name } ]"
1537+ return display
1538+
1539+ # Add current project sessions
1540+ if current_sessions :
1541+ choices .append (questionary .Separator ("── Current Project ──" ))
1542+ for filepath , summary in current_sessions :
1543+ display = format_session (filepath , summary , include_project = False )
1544+ choices .append (questionary .Choice (title = display , value = filepath ))
1545+ elif project_exists :
1546+ choices .append (questionary .Separator ("── Current Project ──" ))
1547+ choices .append (questionary .Separator (" (no sessions found)" ))
1548+ else :
1549+ choices .append (questionary .Separator ("── No sessions for this project ──" ))
1550+
1551+ # Add other project sessions
1552+ if other_sessions :
1553+ choices .append (questionary .Separator ("── Other Projects ──" ))
1554+ for filepath , summary in other_sessions :
1555+ display = format_session (filepath , summary , include_project = True )
1556+ choices .append (questionary .Choice (title = display , value = filepath ))
1557+
1558+ # Handle case where no sessions at all
1559+ if not current_sessions and not other_sessions :
1560+ click .echo ("No local sessions found." )
1561+ return
14531562
14541563 selected = questionary .select (
14551564 "Select a session to convert:" ,
0 commit comments