claude-code-tools 0.2.4__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.

@@ -1,3 +1,3 @@
1
1
  """Claude Code Tools - Collection of utilities for Claude Code."""
2
2
 
3
- __version__ = "0.2.4"
3
+ __version__ = "0.2.5"
@@ -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=f"Sessions matching: {', '.join(keywords)}",
349
+ title=title,
329
350
  box=box.ROUNDED,
330
351
  show_header=True,
331
352
  header_style="bold cyan"
@@ -609,7 +630,9 @@ To persist directory changes when resuming sessions:
609
630
  )
610
631
  parser.add_argument(
611
632
  "keywords",
612
- help="Comma-separated keywords to search for (case-insensitive)"
633
+ nargs='?',
634
+ default="",
635
+ help="Comma-separated keywords to search for (case-insensitive). If omitted, shows all sessions."
613
636
  )
614
637
  parser.add_argument(
615
638
  "-g", "--global",
@@ -636,11 +659,7 @@ To persist directory changes when resuming sessions:
636
659
  args = parser.parse_args()
637
660
 
638
661
  # Parse keywords
639
- keywords = [k.strip() for k in args.keywords.split(",") if k.strip()]
640
-
641
- if not keywords:
642
- print("Error: No keywords provided", file=sys.stderr)
643
- sys.exit(1)
662
+ keywords = [k.strip() for k in args.keywords.split(",") if k.strip()] if args.keywords else []
644
663
 
645
664
  # Check if searching current project only
646
665
  if not getattr(args, 'global'):
@@ -656,10 +675,11 @@ To persist directory changes when resuming sessions:
656
675
 
657
676
  if not matching_sessions:
658
677
  scope = "all projects" if getattr(args, 'global') else "current project"
678
+ keyword_msg = f" containing all keywords: {', '.join(keywords)}" if keywords else ""
659
679
  if RICH_AVAILABLE and console and not args.shell:
660
- console.print(f"[yellow]No sessions found containing all keywords in {scope}:[/yellow] {', '.join(keywords)}")
680
+ console.print(f"[yellow]No sessions found{keyword_msg} in {scope}[/yellow]")
661
681
  else:
662
- print(f"No sessions found containing all keywords in {scope}: {', '.join(keywords)}", file=sys.stderr)
682
+ print(f"No sessions found{keyword_msg} in {scope}", file=sys.stderr)
663
683
  sys.exit(0)
664
684
 
665
685
  # If we have rich and there are results, show interactive UI
@@ -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
- table = Table(title="Codex Sessions", show_header=True)
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
- help="Comma-separated keywords to search (AND logic)",
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.4
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
- # Search across all Claude projects
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=HuiSMt5zAxFI3fikxnQm76eeWOxKqU_2_3ECyCiwX74,89
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=vSVAoQmmLfGXcH_Tk-7jQLYqlDPH5gQwH0VG9lDUm28,30637
6
- claude_code_tools/find_codex_session.py,sha256=uSXtg9CFZ7mdkIoYiVCqUHLTJMDT4RoI_iDdkVeNxao,18957
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.4.dist-info/METADATA,sha256=v55YxqLvZEn0v9BH2XECGbMUScd4GuFfbvv5DP0PvjE,17530
19
- claude_code_tools-0.2.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
20
- claude_code_tools-0.2.4.dist-info/entry_points.txt,sha256=rAHzNUN7b_HIRbFlvpYwK38FG6jREYWaO0ssnhAVPrg,287
21
- claude_code_tools-0.2.4.dist-info/licenses/LICENSE,sha256=BBQdOBLdFB3CEPmb3pqxeOThaFCIdsiLzmDANsCHhoM,1073
22
- claude_code_tools-0.2.4.dist-info/RECORD,,
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,,