claude-code-tools 0.2.3__py3-none-any.whl → 0.2.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of claude-code-tools might be problematic. Click here for more details.
- claude_code_tools/__init__.py +1 -1
- claude_code_tools/find_claude_session.py +59 -28
- claude_code_tools/find_codex_session.py +43 -8
- {claude_code_tools-0.2.3.dist-info → claude_code_tools-0.2.5.dist-info}/METADATA +16 -2
- {claude_code_tools-0.2.3.dist-info → claude_code_tools-0.2.5.dist-info}/RECORD +8 -8
- {claude_code_tools-0.2.3.dist-info → claude_code_tools-0.2.5.dist-info}/WHEEL +0 -0
- {claude_code_tools-0.2.3.dist-info → claude_code_tools-0.2.5.dist-info}/entry_points.txt +0 -0
- {claude_code_tools-0.2.3.dist-info → claude_code_tools-0.2.5.dist-info}/licenses/LICENSE +0 -0
claude_code_tools/__init__.py
CHANGED
|
@@ -124,29 +124,49 @@ def extract_project_name(original_path: str) -> str:
|
|
|
124
124
|
def search_keywords_in_file(filepath: Path, keywords: List[str]) -> tuple[bool, int, Optional[str]]:
|
|
125
125
|
"""
|
|
126
126
|
Check if all keywords are present in the JSONL file, count lines, and extract git branch.
|
|
127
|
-
|
|
127
|
+
|
|
128
128
|
Args:
|
|
129
129
|
filepath: Path to the JSONL file
|
|
130
|
-
keywords: List of keywords to search for (case-insensitive)
|
|
131
|
-
|
|
130
|
+
keywords: List of keywords to search for (case-insensitive). Empty list matches all files.
|
|
131
|
+
|
|
132
132
|
Returns:
|
|
133
133
|
Tuple of (matches: bool, line_count: int, git_branch: Optional[str])
|
|
134
|
-
- matches: True if ALL keywords are found in the file
|
|
134
|
+
- matches: True if ALL keywords are found in the file (or True if no keywords)
|
|
135
135
|
- line_count: Total number of lines in the file
|
|
136
136
|
- git_branch: Git branch name from the first message that has it, or None
|
|
137
137
|
"""
|
|
138
|
+
# If no keywords, match all files
|
|
139
|
+
if not keywords:
|
|
140
|
+
line_count = 0
|
|
141
|
+
git_branch = None
|
|
142
|
+
try:
|
|
143
|
+
with open(filepath, 'r', encoding='utf-8') as f:
|
|
144
|
+
for line in f:
|
|
145
|
+
line_count += 1
|
|
146
|
+
# Extract git branch from JSON if not already found
|
|
147
|
+
if git_branch is None:
|
|
148
|
+
try:
|
|
149
|
+
data = json.loads(line.strip())
|
|
150
|
+
if 'gitBranch' in data and data['gitBranch']:
|
|
151
|
+
git_branch = data['gitBranch']
|
|
152
|
+
except (json.JSONDecodeError, KeyError):
|
|
153
|
+
pass
|
|
154
|
+
except Exception:
|
|
155
|
+
return False, 0, None
|
|
156
|
+
return True, line_count, git_branch
|
|
157
|
+
|
|
138
158
|
# Convert keywords to lowercase for case-insensitive search
|
|
139
159
|
keywords_lower = [k.lower() for k in keywords]
|
|
140
160
|
found_keywords = set()
|
|
141
161
|
line_count = 0
|
|
142
162
|
git_branch = None
|
|
143
|
-
|
|
163
|
+
|
|
144
164
|
try:
|
|
145
165
|
with open(filepath, 'r', encoding='utf-8') as f:
|
|
146
166
|
for line in f:
|
|
147
167
|
line_count += 1
|
|
148
168
|
line_lower = line.lower()
|
|
149
|
-
|
|
169
|
+
|
|
150
170
|
# Extract git branch from JSON if not already found
|
|
151
171
|
if git_branch is None:
|
|
152
172
|
try:
|
|
@@ -155,7 +175,7 @@ def search_keywords_in_file(filepath: Path, keywords: List[str]) -> tuple[bool,
|
|
|
155
175
|
git_branch = data['gitBranch']
|
|
156
176
|
except (json.JSONDecodeError, KeyError):
|
|
157
177
|
pass
|
|
158
|
-
|
|
178
|
+
|
|
159
179
|
# Check which keywords are in this line
|
|
160
180
|
for keyword in keywords_lower:
|
|
161
181
|
if keyword in line_lower:
|
|
@@ -163,7 +183,7 @@ def search_keywords_in_file(filepath: Path, keywords: List[str]) -> tuple[bool,
|
|
|
163
183
|
except Exception:
|
|
164
184
|
# Skip files that can't be read
|
|
165
185
|
return False, 0, None
|
|
166
|
-
|
|
186
|
+
|
|
167
187
|
matches = len(found_keywords) == len(keywords_lower)
|
|
168
188
|
return matches, line_count, git_branch
|
|
169
189
|
|
|
@@ -310,22 +330,23 @@ def display_interactive_ui(sessions: List[Tuple[str, float, int, str, str, str,
|
|
|
310
330
|
"""Display interactive UI for session selection."""
|
|
311
331
|
if not RICH_AVAILABLE:
|
|
312
332
|
return None
|
|
313
|
-
|
|
333
|
+
|
|
314
334
|
# Use stderr console if in stderr mode
|
|
315
335
|
ui_console = Console(file=sys.stderr) if stderr_mode else console
|
|
316
336
|
if not ui_console:
|
|
317
337
|
return None
|
|
318
|
-
|
|
338
|
+
|
|
319
339
|
# Limit to specified number of sessions
|
|
320
340
|
display_sessions = sessions[:num_matches]
|
|
321
|
-
|
|
341
|
+
|
|
322
342
|
if not display_sessions:
|
|
323
343
|
ui_console.print("[red]No sessions found[/red]")
|
|
324
344
|
return None
|
|
325
|
-
|
|
345
|
+
|
|
326
346
|
# Create table
|
|
347
|
+
title = f"Sessions matching: {', '.join(keywords)}" if keywords else "All sessions"
|
|
327
348
|
table = Table(
|
|
328
|
-
title=
|
|
349
|
+
title=title,
|
|
329
350
|
box=box.ROUNDED,
|
|
330
351
|
show_header=True,
|
|
331
352
|
header_style="bold cyan"
|
|
@@ -509,15 +530,20 @@ def copy_session_file(session_file_path: str) -> None:
|
|
|
509
530
|
print(f"\nError copying file: {e}")
|
|
510
531
|
|
|
511
532
|
|
|
512
|
-
def resume_session(session_id: str, project_path: str, shell_mode: bool = False):
|
|
533
|
+
def resume_session(session_id: str, project_path: str, shell_mode: bool = False, claude_home: Optional[str] = None):
|
|
513
534
|
"""Resume a Claude session using claude -r command."""
|
|
514
535
|
current_dir = os.getcwd()
|
|
515
|
-
|
|
536
|
+
|
|
516
537
|
# In shell mode, output commands for the shell to evaluate
|
|
517
538
|
if shell_mode:
|
|
518
539
|
if project_path != current_dir:
|
|
519
540
|
print(f'cd {shlex.quote(project_path)}')
|
|
520
|
-
|
|
541
|
+
# Set CLAUDE_CONFIG_DIR environment variable if custom path specified
|
|
542
|
+
if claude_home:
|
|
543
|
+
expanded_home = str(Path(claude_home).expanduser().absolute())
|
|
544
|
+
print(f'CLAUDE_CONFIG_DIR={shlex.quote(expanded_home)} claude -r {shlex.quote(session_id)}')
|
|
545
|
+
else:
|
|
546
|
+
print(f'claude -r {shlex.quote(session_id)}')
|
|
521
547
|
return
|
|
522
548
|
|
|
523
549
|
# Check if we need to change directory
|
|
@@ -560,7 +586,13 @@ def resume_session(session_id: str, project_path: str, shell_mode: bool = False)
|
|
|
560
586
|
# Change directory if needed (won't persist after exit)
|
|
561
587
|
if change_dir and project_path != current_dir:
|
|
562
588
|
os.chdir(project_path)
|
|
563
|
-
|
|
589
|
+
|
|
590
|
+
# Set CLAUDE_CONFIG_DIR environment variable if custom path specified
|
|
591
|
+
if claude_home:
|
|
592
|
+
# Expand ~ and make absolute
|
|
593
|
+
expanded_home = str(Path(claude_home).expanduser().absolute())
|
|
594
|
+
os.environ['CLAUDE_CONFIG_DIR'] = expanded_home
|
|
595
|
+
|
|
564
596
|
# Execute claude
|
|
565
597
|
os.execvp("claude", ["claude", "-r", session_id])
|
|
566
598
|
|
|
@@ -598,7 +630,9 @@ To persist directory changes when resuming sessions:
|
|
|
598
630
|
)
|
|
599
631
|
parser.add_argument(
|
|
600
632
|
"keywords",
|
|
601
|
-
|
|
633
|
+
nargs='?',
|
|
634
|
+
default="",
|
|
635
|
+
help="Comma-separated keywords to search for (case-insensitive). If omitted, shows all sessions."
|
|
602
636
|
)
|
|
603
637
|
parser.add_argument(
|
|
604
638
|
"-g", "--global",
|
|
@@ -625,11 +659,7 @@ To persist directory changes when resuming sessions:
|
|
|
625
659
|
args = parser.parse_args()
|
|
626
660
|
|
|
627
661
|
# Parse keywords
|
|
628
|
-
keywords = [k.strip() for k in args.keywords.split(",") if k.strip()]
|
|
629
|
-
|
|
630
|
-
if not keywords:
|
|
631
|
-
print("Error: No keywords provided", file=sys.stderr)
|
|
632
|
-
sys.exit(1)
|
|
662
|
+
keywords = [k.strip() for k in args.keywords.split(",") if k.strip()] if args.keywords else []
|
|
633
663
|
|
|
634
664
|
# Check if searching current project only
|
|
635
665
|
if not getattr(args, 'global'):
|
|
@@ -645,10 +675,11 @@ To persist directory changes when resuming sessions:
|
|
|
645
675
|
|
|
646
676
|
if not matching_sessions:
|
|
647
677
|
scope = "all projects" if getattr(args, 'global') else "current project"
|
|
678
|
+
keyword_msg = f" containing all keywords: {', '.join(keywords)}" if keywords else ""
|
|
648
679
|
if RICH_AVAILABLE and console and not args.shell:
|
|
649
|
-
console.print(f"[yellow]No sessions found
|
|
680
|
+
console.print(f"[yellow]No sessions found{keyword_msg} in {scope}[/yellow]")
|
|
650
681
|
else:
|
|
651
|
-
print(f"No sessions found
|
|
682
|
+
print(f"No sessions found{keyword_msg} in {scope}", file=sys.stderr)
|
|
652
683
|
sys.exit(0)
|
|
653
684
|
|
|
654
685
|
# If we have rich and there are results, show interactive UI
|
|
@@ -665,7 +696,7 @@ To persist directory changes when resuming sessions:
|
|
|
665
696
|
|
|
666
697
|
# Perform selected action
|
|
667
698
|
if action == "resume":
|
|
668
|
-
resume_session(session_id, project_path, shell_mode=args.shell)
|
|
699
|
+
resume_session(session_id, project_path, shell_mode=args.shell, claude_home=args.claude_home)
|
|
669
700
|
elif action == "path":
|
|
670
701
|
session_file_path = get_session_file_path(session_id, project_path, args.claude_home)
|
|
671
702
|
print(f"\nSession file path:")
|
|
@@ -695,7 +726,7 @@ To persist directory changes when resuming sessions:
|
|
|
695
726
|
if not args.shell:
|
|
696
727
|
print("\nOnly one match found. Resuming automatically...")
|
|
697
728
|
session_id, _, _, _, _, _, project_path, _ = matching_sessions[0]
|
|
698
|
-
resume_session(session_id, project_path, shell_mode=args.shell)
|
|
729
|
+
resume_session(session_id, project_path, shell_mode=args.shell, claude_home=args.claude_home)
|
|
699
730
|
else:
|
|
700
731
|
try:
|
|
701
732
|
if args.shell:
|
|
@@ -714,7 +745,7 @@ To persist directory changes when resuming sessions:
|
|
|
714
745
|
idx = int(choice) - 1
|
|
715
746
|
if 0 <= idx < min(args.num_matches, len(matching_sessions)):
|
|
716
747
|
session_id, _, _, _, _, project_path, _ = matching_sessions[idx]
|
|
717
|
-
resume_session(session_id, project_path, shell_mode=args.shell)
|
|
748
|
+
resume_session(session_id, project_path, shell_mode=args.shell, claude_home=args.claude_home)
|
|
718
749
|
else:
|
|
719
750
|
print("Invalid choice", file=sys.stderr)
|
|
720
751
|
sys.exit(1)
|
|
@@ -112,10 +112,44 @@ def search_keywords_in_file(
|
|
|
112
112
|
Search for keywords in a Codex session file.
|
|
113
113
|
|
|
114
114
|
Returns: (found, line_count, preview)
|
|
115
|
-
- found: True if all keywords found (case-insensitive AND logic)
|
|
115
|
+
- found: True if all keywords found (case-insensitive AND logic), or True if no keywords
|
|
116
116
|
- line_count: total lines in file
|
|
117
117
|
- preview: best user message content (skips system messages)
|
|
118
118
|
"""
|
|
119
|
+
# If no keywords, match all files
|
|
120
|
+
if not keywords:
|
|
121
|
+
line_count = 0
|
|
122
|
+
last_user_message = None
|
|
123
|
+
try:
|
|
124
|
+
with open(session_file, "r", encoding="utf-8") as f:
|
|
125
|
+
for line in f:
|
|
126
|
+
line_count += 1
|
|
127
|
+
if not line.strip():
|
|
128
|
+
continue
|
|
129
|
+
try:
|
|
130
|
+
entry = json.loads(line)
|
|
131
|
+
# Extract user messages (skip system messages)
|
|
132
|
+
if (
|
|
133
|
+
entry.get("type") == "response_item"
|
|
134
|
+
and entry.get("payload", {}).get("role") == "user"
|
|
135
|
+
):
|
|
136
|
+
content = entry.get("payload", {}).get("content", [])
|
|
137
|
+
if isinstance(content, list) and len(content) > 0:
|
|
138
|
+
first_item = content[0]
|
|
139
|
+
if isinstance(first_item, dict):
|
|
140
|
+
text = first_item.get("text", "")
|
|
141
|
+
if text and not is_system_message(text):
|
|
142
|
+
cleaned = text[:400].replace("\n", " ").strip()
|
|
143
|
+
if len(cleaned) > 20:
|
|
144
|
+
last_user_message = cleaned
|
|
145
|
+
elif last_user_message is None:
|
|
146
|
+
last_user_message = cleaned
|
|
147
|
+
except json.JSONDecodeError:
|
|
148
|
+
continue
|
|
149
|
+
return True, line_count, last_user_message
|
|
150
|
+
except (OSError, IOError):
|
|
151
|
+
return False, 0, None
|
|
152
|
+
|
|
119
153
|
keywords_lower = [k.lower() for k in keywords]
|
|
120
154
|
found_keywords = set()
|
|
121
155
|
line_count = 0
|
|
@@ -277,6 +311,7 @@ def find_sessions(
|
|
|
277
311
|
|
|
278
312
|
def display_interactive_ui(
|
|
279
313
|
matches: list[dict],
|
|
314
|
+
keywords: list[str] = None,
|
|
280
315
|
) -> Optional[dict]:
|
|
281
316
|
"""
|
|
282
317
|
Display matches in interactive UI and get user selection.
|
|
@@ -289,7 +324,8 @@ def display_interactive_ui(
|
|
|
289
324
|
|
|
290
325
|
if RICH_AVAILABLE:
|
|
291
326
|
console = Console()
|
|
292
|
-
|
|
327
|
+
title = f"Codex Sessions matching: {', '.join(keywords)}" if keywords else "All Codex Sessions"
|
|
328
|
+
table = Table(title=title, show_header=True)
|
|
293
329
|
table.add_column("#", style="cyan", justify="right")
|
|
294
330
|
table.add_column("Session ID", style="yellow", no_wrap=True)
|
|
295
331
|
table.add_column("Project", style="green")
|
|
@@ -486,7 +522,9 @@ Examples:
|
|
|
486
522
|
|
|
487
523
|
parser.add_argument(
|
|
488
524
|
"keywords",
|
|
489
|
-
|
|
525
|
+
nargs='?',
|
|
526
|
+
default="",
|
|
527
|
+
help="Comma-separated keywords to search (AND logic). If omitted, shows all sessions.",
|
|
490
528
|
)
|
|
491
529
|
parser.add_argument(
|
|
492
530
|
"-g",
|
|
@@ -515,10 +553,7 @@ Examples:
|
|
|
515
553
|
args = parser.parse_args()
|
|
516
554
|
|
|
517
555
|
# Parse keywords
|
|
518
|
-
keywords = [k.strip() for k in args.keywords.split(",") if k.strip()]
|
|
519
|
-
if not keywords:
|
|
520
|
-
print("Error: No keywords provided", file=sys.stderr)
|
|
521
|
-
sys.exit(1)
|
|
556
|
+
keywords = [k.strip() for k in args.keywords.split(",") if k.strip()] if args.keywords else []
|
|
522
557
|
|
|
523
558
|
# Get Codex home
|
|
524
559
|
codex_home = get_codex_home(args.codex_home)
|
|
@@ -532,7 +567,7 @@ Examples:
|
|
|
532
567
|
)
|
|
533
568
|
|
|
534
569
|
# Display and get selection
|
|
535
|
-
selected_match = display_interactive_ui(matches)
|
|
570
|
+
selected_match = display_interactive_ui(matches, keywords)
|
|
536
571
|
if not selected_match:
|
|
537
572
|
return
|
|
538
573
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: claude-code-tools
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
4
4
|
Summary: Collection of tools for working with Claude Code
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -208,13 +208,20 @@ source /path/to/claude-code-tools/scripts/fcs-function.sh
|
|
|
208
208
|
# Search in current project
|
|
209
209
|
fcs "keyword1,keyword2,keyword3"
|
|
210
210
|
|
|
211
|
-
#
|
|
211
|
+
# Show all sessions in current project (no keyword filtering)
|
|
212
|
+
fcs
|
|
213
|
+
|
|
214
|
+
# Search across all Claude projects
|
|
212
215
|
fcs "keywords" --global
|
|
213
216
|
fcs "keywords" -g
|
|
217
|
+
|
|
218
|
+
# Show all sessions across all projects
|
|
219
|
+
fcs -g
|
|
214
220
|
```
|
|
215
221
|
|
|
216
222
|
### Features
|
|
217
223
|
|
|
224
|
+
- **Optional keyword search**: Keywords are optional—omit them to show all sessions
|
|
218
225
|
- **Action menu** after session selection:
|
|
219
226
|
- Resume session (default)
|
|
220
227
|
- Show session file path
|
|
@@ -252,10 +259,16 @@ Search and resume Codex sessions by keywords. Usage is similar to `find-claude-s
|
|
|
252
259
|
# Search in current project only (default)
|
|
253
260
|
find-codex-session "keyword1,keyword2"
|
|
254
261
|
|
|
262
|
+
# Show all sessions in current project (no keyword filtering)
|
|
263
|
+
find-codex-session
|
|
264
|
+
|
|
255
265
|
# Search across all projects
|
|
256
266
|
find-codex-session "keywords" -g
|
|
257
267
|
find-codex-session "keywords" --global
|
|
258
268
|
|
|
269
|
+
# Show all sessions across all projects
|
|
270
|
+
find-codex-session -g
|
|
271
|
+
|
|
259
272
|
# Limit number of results
|
|
260
273
|
find-codex-session "keywords" -n 5
|
|
261
274
|
|
|
@@ -265,6 +278,7 @@ find-codex-session "keywords" --codex-home /custom/path
|
|
|
265
278
|
|
|
266
279
|
### Features
|
|
267
280
|
|
|
281
|
+
- **Optional keyword search**: Keywords are optional—omit them to show all sessions
|
|
268
282
|
- **Action menu** after session selection:
|
|
269
283
|
- Resume session (default)
|
|
270
284
|
- Show session file path
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
claude_code_tools/__init__.py,sha256=
|
|
1
|
+
claude_code_tools/__init__.py,sha256=0G7bWv5VJixTjc4F_ntWd_gL1ff2ndWml5zVHh1K10U,89
|
|
2
2
|
claude_code_tools/codex_bridge_mcp.py,sha256=0roYm3YgEFB6y2MvGovzHyY7avKtire4qBtz3kVaYoY,12596
|
|
3
3
|
claude_code_tools/dotenv_vault.py,sha256=KPI9NDFu5HE6FfhQUYw6RhdR-miN0ScJHsBg0OVG61k,9617
|
|
4
4
|
claude_code_tools/env_safe.py,sha256=TSSkOjEpzBwNgbeSR-0tR1-pAW_qmbZNmn3fiAsHJ4w,7659
|
|
5
|
-
claude_code_tools/find_claude_session.py,sha256=
|
|
6
|
-
claude_code_tools/find_codex_session.py,sha256=
|
|
5
|
+
claude_code_tools/find_claude_session.py,sha256=Mc75CnAdp8mgO9y2s1_oG1rH9svZAUAdqOg_7ezkRg4,31482
|
|
6
|
+
claude_code_tools/find_codex_session.py,sha256=oRSHux_0kwSxKZe5m_Nzjx2aV0b--cVzPJ-w6RXLh0c,20791
|
|
7
7
|
claude_code_tools/tmux_cli_controller.py,sha256=5QDrDlv3oabIghRHuP8jMhUfxPeyYZxizNWW5sVuJIg,34607
|
|
8
8
|
claude_code_tools/tmux_remote_controller.py,sha256=eY1ouLtUzJ40Ik4nqUBvc3Gl1Rx0_L4TFW4j708lgvI,9942
|
|
9
9
|
docs/cc-codex-instructions.md,sha256=5E9QotkrcVYIE5VrvJGi-sg7tdyITDrsbhaqBKr4MUk,1109
|
|
@@ -15,8 +15,8 @@ docs/lmsh.md,sha256=o2TNP1Yfl3zW23GzEqK8Bx6z1hQof_lplaeEucuHNRU,1335
|
|
|
15
15
|
docs/reddit-post.md,sha256=ZA7kPoJNi06t6F9JQMBiIOv039ADC9lM8YXFt8UA_Jg,2345
|
|
16
16
|
docs/tmux-cli-instructions.md,sha256=hKGOdaPdBlb5XFzHfi0Mm7CVlysBuJUAfop3GHreyuw,5008
|
|
17
17
|
docs/vault-documentation.md,sha256=5XzNpHyhGU38JU2hKEWEL1gdPq3rC2zBg8yotK4eNF4,3600
|
|
18
|
-
claude_code_tools-0.2.
|
|
19
|
-
claude_code_tools-0.2.
|
|
20
|
-
claude_code_tools-0.2.
|
|
21
|
-
claude_code_tools-0.2.
|
|
22
|
-
claude_code_tools-0.2.
|
|
18
|
+
claude_code_tools-0.2.5.dist-info/METADATA,sha256=Yxzyidh8hiBahZa1Bzusr2Wo53OqOxldYbmz62zHIas,17960
|
|
19
|
+
claude_code_tools-0.2.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
20
|
+
claude_code_tools-0.2.5.dist-info/entry_points.txt,sha256=rAHzNUN7b_HIRbFlvpYwK38FG6jREYWaO0ssnhAVPrg,287
|
|
21
|
+
claude_code_tools-0.2.5.dist-info/licenses/LICENSE,sha256=BBQdOBLdFB3CEPmb3pqxeOThaFCIdsiLzmDANsCHhoM,1073
|
|
22
|
+
claude_code_tools-0.2.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|