claude-code-tools 0.1.13__tar.gz → 0.1.17__tar.gz
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-0.1.17/LICENSE +21 -0
- {claude_code_tools-0.1.13 → claude_code_tools-0.1.17}/PKG-INFO +36 -1
- {claude_code_tools-0.1.13 → claude_code_tools-0.1.17}/README.md +34 -0
- {claude_code_tools-0.1.13 → claude_code_tools-0.1.17}/claude_code_tools/__init__.py +1 -1
- claude_code_tools-0.1.17/claude_code_tools/env_safe.py +235 -0
- {claude_code_tools-0.1.13 → claude_code_tools-0.1.17}/claude_code_tools/find_claude_session.py +50 -21
- {claude_code_tools-0.1.13 → claude_code_tools-0.1.17}/claude_code_tools/tmux_cli_controller.py +20 -0
- claude_code_tools-0.1.17/docs/dot-zshrc.md +307 -0
- {claude_code_tools-0.1.13 → claude_code_tools-0.1.17}/pyproject.toml +3 -2
- {claude_code_tools-0.1.13 → claude_code_tools-0.1.17}/.gitignore +0 -0
- {claude_code_tools-0.1.13 → claude_code_tools-0.1.17}/claude_code_tools/dotenv_vault.py +0 -0
- {claude_code_tools-0.1.13 → claude_code_tools-0.1.17}/claude_code_tools/tmux_remote_controller.py +0 -0
- {claude_code_tools-0.1.13 → claude_code_tools-0.1.17}/docs/claude-code-chutes.md +0 -0
- {claude_code_tools-0.1.13 → claude_code_tools-0.1.17}/docs/claude-code-tmux-tutorials.md +0 -0
- {claude_code_tools-0.1.13 → claude_code_tools-0.1.17}/docs/find-claude-session.md +0 -0
- {claude_code_tools-0.1.13 → claude_code_tools-0.1.17}/docs/reddit-post.md +0 -0
- {claude_code_tools-0.1.13 → claude_code_tools-0.1.17}/docs/tmux-cli-instructions.md +0 -0
- {claude_code_tools-0.1.13 → claude_code_tools-0.1.17}/docs/vault-documentation.md +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Prasad Chalasani
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: claude-code-tools
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.17
|
|
4
4
|
Summary: Collection of tools for working with Claude Code
|
|
5
|
+
License-File: LICENSE
|
|
5
6
|
Requires-Python: >=3.11
|
|
6
7
|
Requires-Dist: click>=8.0.0
|
|
7
8
|
Requires-Dist: fire>=0.5.0
|
|
@@ -62,6 +63,7 @@ This gives you:
|
|
|
62
63
|
- `tmux-cli` - The interactive CLI controller we just covered
|
|
63
64
|
- `find-claude-session` - Search and resume Claude Code sessions by keywords
|
|
64
65
|
- `vault` - Encrypted backup for your .env files
|
|
66
|
+
- `env-safe` - Safely inspect .env files without exposing values
|
|
65
67
|
|
|
66
68
|
## 🎮 tmux-cli Deep Dive
|
|
67
69
|
|
|
@@ -106,6 +108,12 @@ Example uses:
|
|
|
106
108
|
- Launch web apps and test them with browser automation MCP tools like Puppeteer
|
|
107
109
|
```
|
|
108
110
|
|
|
111
|
+
Incidentally, installing the Puppeteer MCP tool is easy:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
claude mcp add puppeteer -- npx -y @modelcontextprotocol/server-puppeteer
|
|
115
|
+
```
|
|
116
|
+
|
|
109
117
|
For detailed instructions, see [docs/tmux-cli-instructions.md](docs/tmux-cli-instructions.md).
|
|
110
118
|
|
|
111
119
|
## 🔍 find-claude-session
|
|
@@ -182,6 +190,31 @@ vault status # Check sync status for current project
|
|
|
182
190
|
|
|
183
191
|
For detailed documentation, see [docs/vault-documentation.md](docs/vault-documentation.md).
|
|
184
192
|
|
|
193
|
+
## 🔍 env-safe
|
|
194
|
+
|
|
195
|
+
Safely inspect .env files without exposing sensitive values. Designed for Claude Code and other automated tools that need to work with environment files without accidentally leaking secrets.
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
env-safe list # List all environment variable keys
|
|
199
|
+
env-safe list --status # Show keys with defined/empty status
|
|
200
|
+
env-safe check API_KEY # Check if a specific key exists
|
|
201
|
+
env-safe count # Count total, defined, and empty variables
|
|
202
|
+
env-safe validate # Validate .env file syntax
|
|
203
|
+
env-safe --help # See all options
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Key Features
|
|
207
|
+
|
|
208
|
+
- **No Value Exposure** - Never displays actual environment values
|
|
209
|
+
- **Safe Inspection** - Check which keys exist without security risks
|
|
210
|
+
- **Syntax Validation** - Verify .env file format is correct
|
|
211
|
+
- **Status Checking** - See which variables are defined vs empty
|
|
212
|
+
- **Claude Code Integration** - Works with protection hooks to provide safe alternative
|
|
213
|
+
|
|
214
|
+
### Why env-safe?
|
|
215
|
+
|
|
216
|
+
When Claude Code attempts to read .env files directly (via cat, grep, etc.), safety hooks block the operation to prevent accidental exposure of API keys and secrets. The `env-safe` command provides a secure alternative that lets Claude Code inspect environment configuration without security risks.
|
|
217
|
+
|
|
185
218
|
## 🛡️ Claude Code Safety Hooks
|
|
186
219
|
|
|
187
220
|
This repository includes a comprehensive set of safety hooks that enhance Claude
|
|
@@ -193,6 +226,8 @@ Code's behavior and prevent dangerous operations.
|
|
|
193
226
|
pattern
|
|
194
227
|
- **Git Safety** - Prevents dangerous `git add -A`, unsafe checkouts, and
|
|
195
228
|
accidental data loss
|
|
229
|
+
- **Environment Security** - Blocks direct .env file access, suggests `env-safe`
|
|
230
|
+
command instead
|
|
196
231
|
- **Context Management** - Blocks reading files >500 lines to prevent context
|
|
197
232
|
bloat
|
|
198
233
|
- **Command Enhancement** - Enforces ripgrep (`rg`) over grep for better
|
|
@@ -50,6 +50,7 @@ This gives you:
|
|
|
50
50
|
- `tmux-cli` - The interactive CLI controller we just covered
|
|
51
51
|
- `find-claude-session` - Search and resume Claude Code sessions by keywords
|
|
52
52
|
- `vault` - Encrypted backup for your .env files
|
|
53
|
+
- `env-safe` - Safely inspect .env files without exposing values
|
|
53
54
|
|
|
54
55
|
## 🎮 tmux-cli Deep Dive
|
|
55
56
|
|
|
@@ -94,6 +95,12 @@ Example uses:
|
|
|
94
95
|
- Launch web apps and test them with browser automation MCP tools like Puppeteer
|
|
95
96
|
```
|
|
96
97
|
|
|
98
|
+
Incidentally, installing the Puppeteer MCP tool is easy:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
claude mcp add puppeteer -- npx -y @modelcontextprotocol/server-puppeteer
|
|
102
|
+
```
|
|
103
|
+
|
|
97
104
|
For detailed instructions, see [docs/tmux-cli-instructions.md](docs/tmux-cli-instructions.md).
|
|
98
105
|
|
|
99
106
|
## 🔍 find-claude-session
|
|
@@ -170,6 +177,31 @@ vault status # Check sync status for current project
|
|
|
170
177
|
|
|
171
178
|
For detailed documentation, see [docs/vault-documentation.md](docs/vault-documentation.md).
|
|
172
179
|
|
|
180
|
+
## 🔍 env-safe
|
|
181
|
+
|
|
182
|
+
Safely inspect .env files without exposing sensitive values. Designed for Claude Code and other automated tools that need to work with environment files without accidentally leaking secrets.
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
env-safe list # List all environment variable keys
|
|
186
|
+
env-safe list --status # Show keys with defined/empty status
|
|
187
|
+
env-safe check API_KEY # Check if a specific key exists
|
|
188
|
+
env-safe count # Count total, defined, and empty variables
|
|
189
|
+
env-safe validate # Validate .env file syntax
|
|
190
|
+
env-safe --help # See all options
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Key Features
|
|
194
|
+
|
|
195
|
+
- **No Value Exposure** - Never displays actual environment values
|
|
196
|
+
- **Safe Inspection** - Check which keys exist without security risks
|
|
197
|
+
- **Syntax Validation** - Verify .env file format is correct
|
|
198
|
+
- **Status Checking** - See which variables are defined vs empty
|
|
199
|
+
- **Claude Code Integration** - Works with protection hooks to provide safe alternative
|
|
200
|
+
|
|
201
|
+
### Why env-safe?
|
|
202
|
+
|
|
203
|
+
When Claude Code attempts to read .env files directly (via cat, grep, etc.), safety hooks block the operation to prevent accidental exposure of API keys and secrets. The `env-safe` command provides a secure alternative that lets Claude Code inspect environment configuration without security risks.
|
|
204
|
+
|
|
173
205
|
## 🛡️ Claude Code Safety Hooks
|
|
174
206
|
|
|
175
207
|
This repository includes a comprehensive set of safety hooks that enhance Claude
|
|
@@ -181,6 +213,8 @@ Code's behavior and prevent dangerous operations.
|
|
|
181
213
|
pattern
|
|
182
214
|
- **Git Safety** - Prevents dangerous `git add -A`, unsafe checkouts, and
|
|
183
215
|
accidental data loss
|
|
216
|
+
- **Environment Security** - Blocks direct .env file access, suggests `env-safe`
|
|
217
|
+
command instead
|
|
184
218
|
- **Context Management** - Blocks reading files >500 lines to prevent context
|
|
185
219
|
bloat
|
|
186
220
|
- **Command Enhancement** - Enforces ripgrep (`rg`) over grep for better
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
env-safe: A safe way to inspect .env files without exposing sensitive values.
|
|
4
|
+
|
|
5
|
+
This tool allows Claude Code and other automated tools to:
|
|
6
|
+
- List environment variable keys without showing values
|
|
7
|
+
- Check if specific keys exist
|
|
8
|
+
- Count the number of variables defined
|
|
9
|
+
- Validate .env file syntax
|
|
10
|
+
|
|
11
|
+
It specifically avoids displaying actual values to prevent accidental exposure
|
|
12
|
+
of secrets, API keys, and other sensitive information.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import sys
|
|
17
|
+
import argparse
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import List, Tuple, Optional
|
|
20
|
+
import re
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def parse_env_file(filepath: Path) -> List[Tuple[str, bool]]:
|
|
24
|
+
"""
|
|
25
|
+
Parse a .env file and extract variable names.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
List of tuples: (variable_name, has_value)
|
|
29
|
+
"""
|
|
30
|
+
if not filepath.exists():
|
|
31
|
+
raise FileNotFoundError(f"File not found: {filepath}")
|
|
32
|
+
|
|
33
|
+
variables = []
|
|
34
|
+
|
|
35
|
+
with open(filepath, 'r', encoding='utf-8') as f:
|
|
36
|
+
for line_num, line in enumerate(f, 1):
|
|
37
|
+
# Skip empty lines and comments
|
|
38
|
+
line = line.strip()
|
|
39
|
+
if not line or line.startswith('#'):
|
|
40
|
+
continue
|
|
41
|
+
|
|
42
|
+
# Match KEY=value pattern
|
|
43
|
+
match = re.match(r'^([A-Za-z_][A-Za-z0-9_]*)\s*=(.*)$', line)
|
|
44
|
+
if match:
|
|
45
|
+
key = match.group(1)
|
|
46
|
+
value = match.group(2).strip()
|
|
47
|
+
has_value = bool(value and value != '""' and value != "''")
|
|
48
|
+
variables.append((key, has_value))
|
|
49
|
+
elif '=' in line:
|
|
50
|
+
# Malformed line - has = but doesn't match pattern
|
|
51
|
+
print(f"Warning: Line {line_num} appears malformed: {line[:50]}...",
|
|
52
|
+
file=sys.stderr)
|
|
53
|
+
|
|
54
|
+
return variables
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def list_keys(filepath: Path, show_status: bool = False) -> None:
|
|
58
|
+
"""List all environment variable keys in the file."""
|
|
59
|
+
try:
|
|
60
|
+
variables = parse_env_file(filepath)
|
|
61
|
+
|
|
62
|
+
if not variables:
|
|
63
|
+
print("No environment variables found in file.")
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
if show_status:
|
|
67
|
+
print(f"{'KEY':<30} {'STATUS':<10}")
|
|
68
|
+
print("-" * 40)
|
|
69
|
+
for key, has_value in sorted(variables):
|
|
70
|
+
status = "defined" if has_value else "empty"
|
|
71
|
+
print(f"{key:<30} {status:<10}")
|
|
72
|
+
else:
|
|
73
|
+
for key, _ in sorted(variables):
|
|
74
|
+
print(key)
|
|
75
|
+
|
|
76
|
+
except FileNotFoundError as e:
|
|
77
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
78
|
+
sys.exit(1)
|
|
79
|
+
except Exception as e:
|
|
80
|
+
print(f"Error reading file: {e}", file=sys.stderr)
|
|
81
|
+
sys.exit(1)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def check_key(filepath: Path, key_name: str) -> None:
|
|
85
|
+
"""Check if a specific key exists in the .env file."""
|
|
86
|
+
try:
|
|
87
|
+
variables = parse_env_file(filepath)
|
|
88
|
+
|
|
89
|
+
for key, has_value in variables:
|
|
90
|
+
if key == key_name:
|
|
91
|
+
if has_value:
|
|
92
|
+
print(f"✓ {key_name} is defined with a value")
|
|
93
|
+
else:
|
|
94
|
+
print(f"⚠ {key_name} is defined but empty")
|
|
95
|
+
sys.exit(0)
|
|
96
|
+
|
|
97
|
+
print(f"✗ {key_name} is not defined")
|
|
98
|
+
sys.exit(1)
|
|
99
|
+
|
|
100
|
+
except FileNotFoundError as e:
|
|
101
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
102
|
+
sys.exit(1)
|
|
103
|
+
except Exception as e:
|
|
104
|
+
print(f"Error reading file: {e}", file=sys.stderr)
|
|
105
|
+
sys.exit(1)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def count_variables(filepath: Path) -> None:
|
|
109
|
+
"""Count the number of variables defined."""
|
|
110
|
+
try:
|
|
111
|
+
variables = parse_env_file(filepath)
|
|
112
|
+
total = len(variables)
|
|
113
|
+
with_values = sum(1 for _, has_value in variables if has_value)
|
|
114
|
+
empty = total - with_values
|
|
115
|
+
|
|
116
|
+
print(f"Total variables: {total}")
|
|
117
|
+
if total > 0:
|
|
118
|
+
print(f" With values: {with_values}")
|
|
119
|
+
print(f" Empty: {empty}")
|
|
120
|
+
|
|
121
|
+
except FileNotFoundError as e:
|
|
122
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
123
|
+
sys.exit(1)
|
|
124
|
+
except Exception as e:
|
|
125
|
+
print(f"Error reading file: {e}", file=sys.stderr)
|
|
126
|
+
sys.exit(1)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def validate_syntax(filepath: Path) -> None:
|
|
130
|
+
"""Validate the syntax of the .env file."""
|
|
131
|
+
try:
|
|
132
|
+
if not filepath.exists():
|
|
133
|
+
print(f"Error: File not found: {filepath}", file=sys.stderr)
|
|
134
|
+
sys.exit(1)
|
|
135
|
+
|
|
136
|
+
issues = []
|
|
137
|
+
valid_lines = 0
|
|
138
|
+
|
|
139
|
+
with open(filepath, 'r', encoding='utf-8') as f:
|
|
140
|
+
for line_num, line in enumerate(f, 1):
|
|
141
|
+
original_line = line
|
|
142
|
+
line = line.strip()
|
|
143
|
+
|
|
144
|
+
# Skip empty lines and comments
|
|
145
|
+
if not line or line.startswith('#'):
|
|
146
|
+
continue
|
|
147
|
+
|
|
148
|
+
# Check for valid KEY=value pattern
|
|
149
|
+
if not re.match(r'^[A-Za-z_][A-Za-z0-9_]*\s*=.*$', line):
|
|
150
|
+
if '=' in line:
|
|
151
|
+
issues.append(f"Line {line_num}: Invalid key format")
|
|
152
|
+
else:
|
|
153
|
+
issues.append(f"Line {line_num}: Missing '=' separator")
|
|
154
|
+
else:
|
|
155
|
+
valid_lines += 1
|
|
156
|
+
|
|
157
|
+
if issues:
|
|
158
|
+
print(f"✗ Found {len(issues)} syntax issue(s):")
|
|
159
|
+
for issue in issues[:10]: # Show first 10 issues
|
|
160
|
+
print(f" {issue}")
|
|
161
|
+
if len(issues) > 10:
|
|
162
|
+
print(f" ... and {len(issues) - 10} more")
|
|
163
|
+
sys.exit(1)
|
|
164
|
+
else:
|
|
165
|
+
print(f"✓ Syntax valid ({valid_lines} variables defined)")
|
|
166
|
+
|
|
167
|
+
except Exception as e:
|
|
168
|
+
print(f"Error reading file: {e}", file=sys.stderr)
|
|
169
|
+
sys.exit(1)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def main():
|
|
173
|
+
parser = argparse.ArgumentParser(
|
|
174
|
+
description="Safely inspect .env files without exposing sensitive values",
|
|
175
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
176
|
+
epilog="""
|
|
177
|
+
Examples:
|
|
178
|
+
env-safe list # List all keys
|
|
179
|
+
env-safe list --status # List keys with defined/empty status
|
|
180
|
+
env-safe check API_KEY # Check if API_KEY exists
|
|
181
|
+
env-safe count # Count variables
|
|
182
|
+
env-safe validate # Check syntax
|
|
183
|
+
env-safe list --file config.env # Use different file
|
|
184
|
+
|
|
185
|
+
This tool is designed to be safe for automated tools like Claude Code,
|
|
186
|
+
preventing accidental exposure of sensitive environment values.
|
|
187
|
+
"""
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
parser.add_argument(
|
|
191
|
+
'--file', '-f',
|
|
192
|
+
default='.env',
|
|
193
|
+
help='Path to env file (default: .env)'
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
subparsers = parser.add_subparsers(dest='command', help='Commands')
|
|
197
|
+
|
|
198
|
+
# List command
|
|
199
|
+
list_parser = subparsers.add_parser('list', help='List all environment keys')
|
|
200
|
+
list_parser.add_argument(
|
|
201
|
+
'--status', '-s',
|
|
202
|
+
action='store_true',
|
|
203
|
+
help='Show whether each key has a value'
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# Check command
|
|
207
|
+
check_parser = subparsers.add_parser('check', help='Check if a key exists')
|
|
208
|
+
check_parser.add_argument('key', help='The key name to check')
|
|
209
|
+
|
|
210
|
+
# Count command
|
|
211
|
+
count_parser = subparsers.add_parser('count', help='Count variables')
|
|
212
|
+
|
|
213
|
+
# Validate command
|
|
214
|
+
validate_parser = subparsers.add_parser('validate', help='Validate syntax')
|
|
215
|
+
|
|
216
|
+
args = parser.parse_args()
|
|
217
|
+
|
|
218
|
+
if not args.command:
|
|
219
|
+
parser.print_help()
|
|
220
|
+
sys.exit(1)
|
|
221
|
+
|
|
222
|
+
filepath = Path(args.file)
|
|
223
|
+
|
|
224
|
+
if args.command == 'list':
|
|
225
|
+
list_keys(filepath, args.status)
|
|
226
|
+
elif args.command == 'check':
|
|
227
|
+
check_key(filepath, args.key)
|
|
228
|
+
elif args.command == 'count':
|
|
229
|
+
count_variables(filepath)
|
|
230
|
+
elif args.command == 'validate':
|
|
231
|
+
validate_syntax(filepath)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
if __name__ == '__main__':
|
|
235
|
+
main()
|
{claude_code_tools-0.1.13 → claude_code_tools-0.1.17}/claude_code_tools/find_claude_session.py
RENAMED
|
@@ -121,39 +121,51 @@ def extract_project_name(original_path: str) -> str:
|
|
|
121
121
|
return parts[-1] if parts else "unknown"
|
|
122
122
|
|
|
123
123
|
|
|
124
|
-
def search_keywords_in_file(filepath: Path, keywords: List[str]) -> tuple[bool, int]:
|
|
124
|
+
def search_keywords_in_file(filepath: Path, keywords: List[str]) -> tuple[bool, int, Optional[str]]:
|
|
125
125
|
"""
|
|
126
|
-
Check if all keywords are present in the JSONL file and
|
|
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
130
|
keywords: List of keywords to search for (case-insensitive)
|
|
131
131
|
|
|
132
132
|
Returns:
|
|
133
|
-
Tuple of (matches: bool, line_count: int)
|
|
133
|
+
Tuple of (matches: bool, line_count: int, git_branch: Optional[str])
|
|
134
134
|
- matches: True if ALL keywords are found in the file
|
|
135
135
|
- line_count: Total number of lines in the file
|
|
136
|
+
- git_branch: Git branch name from the first message that has it, or None
|
|
136
137
|
"""
|
|
137
138
|
# Convert keywords to lowercase for case-insensitive search
|
|
138
139
|
keywords_lower = [k.lower() for k in keywords]
|
|
139
140
|
found_keywords = set()
|
|
140
141
|
line_count = 0
|
|
142
|
+
git_branch = None
|
|
141
143
|
|
|
142
144
|
try:
|
|
143
145
|
with open(filepath, 'r', encoding='utf-8') as f:
|
|
144
146
|
for line in f:
|
|
145
147
|
line_count += 1
|
|
146
148
|
line_lower = line.lower()
|
|
149
|
+
|
|
150
|
+
# Extract git branch from JSON if not already found
|
|
151
|
+
if git_branch is None:
|
|
152
|
+
try:
|
|
153
|
+
data = json.loads(line.strip())
|
|
154
|
+
if 'gitBranch' in data and data['gitBranch']:
|
|
155
|
+
git_branch = data['gitBranch']
|
|
156
|
+
except (json.JSONDecodeError, KeyError):
|
|
157
|
+
pass
|
|
158
|
+
|
|
147
159
|
# Check which keywords are in this line
|
|
148
160
|
for keyword in keywords_lower:
|
|
149
161
|
if keyword in line_lower:
|
|
150
162
|
found_keywords.add(keyword)
|
|
151
163
|
except Exception:
|
|
152
164
|
# Skip files that can't be read
|
|
153
|
-
return False, 0
|
|
165
|
+
return False, 0, None
|
|
154
166
|
|
|
155
167
|
matches = len(found_keywords) == len(keywords_lower)
|
|
156
|
-
return matches, line_count
|
|
168
|
+
return matches, line_count, git_branch
|
|
157
169
|
|
|
158
170
|
|
|
159
171
|
def get_session_preview(filepath: Path) -> str:
|
|
@@ -187,7 +199,7 @@ def get_session_preview(filepath: Path) -> str:
|
|
|
187
199
|
return "No preview available"
|
|
188
200
|
|
|
189
201
|
|
|
190
|
-
def find_sessions(keywords: List[str], global_search: bool = False, claude_home: Optional[str] = None) -> List[Tuple[str, float, int, str, str, str]]:
|
|
202
|
+
def find_sessions(keywords: List[str], global_search: bool = False, claude_home: Optional[str] = None) -> List[Tuple[str, float, int, str, str, str, Optional[str]]]:
|
|
191
203
|
"""
|
|
192
204
|
Find all Claude Code sessions containing the specified keywords.
|
|
193
205
|
|
|
@@ -197,7 +209,7 @@ def find_sessions(keywords: List[str], global_search: bool = False, claude_home:
|
|
|
197
209
|
claude_home: Optional custom Claude home directory (defaults to ~/.claude)
|
|
198
210
|
|
|
199
211
|
Returns:
|
|
200
|
-
List of tuples (session_id, modification_time, line_count, project_name, preview, project_path) sorted by modification time
|
|
212
|
+
List of tuples (session_id, modification_time, line_count, project_name, preview, project_path, git_branch) sorted by modification time
|
|
201
213
|
"""
|
|
202
214
|
matching_sessions = []
|
|
203
215
|
|
|
@@ -220,12 +232,12 @@ def find_sessions(keywords: List[str], global_search: bool = False, claude_home:
|
|
|
220
232
|
|
|
221
233
|
# Search all JSONL files in this project directory
|
|
222
234
|
for jsonl_file in project_dir.glob("*.jsonl"):
|
|
223
|
-
matches, line_count = search_keywords_in_file(jsonl_file, keywords)
|
|
235
|
+
matches, line_count, git_branch = search_keywords_in_file(jsonl_file, keywords)
|
|
224
236
|
if matches:
|
|
225
237
|
session_id = jsonl_file.stem
|
|
226
238
|
mod_time = jsonl_file.stat().st_mtime
|
|
227
239
|
preview = get_session_preview(jsonl_file)
|
|
228
|
-
matching_sessions.append((session_id, mod_time, line_count, project_name, preview, original_path))
|
|
240
|
+
matching_sessions.append((session_id, mod_time, line_count, project_name, preview, original_path, git_branch))
|
|
229
241
|
|
|
230
242
|
progress.advance(task)
|
|
231
243
|
else:
|
|
@@ -234,12 +246,12 @@ def find_sessions(keywords: List[str], global_search: bool = False, claude_home:
|
|
|
234
246
|
project_name = extract_project_name(original_path)
|
|
235
247
|
|
|
236
248
|
for jsonl_file in project_dir.glob("*.jsonl"):
|
|
237
|
-
matches, line_count = search_keywords_in_file(jsonl_file, keywords)
|
|
249
|
+
matches, line_count, git_branch = search_keywords_in_file(jsonl_file, keywords)
|
|
238
250
|
if matches:
|
|
239
251
|
session_id = jsonl_file.stem
|
|
240
252
|
mod_time = jsonl_file.stat().st_mtime
|
|
241
253
|
preview = get_session_preview(jsonl_file)
|
|
242
|
-
matching_sessions.append((session_id, mod_time, line_count, project_name, preview, original_path))
|
|
254
|
+
matching_sessions.append((session_id, mod_time, line_count, project_name, preview, original_path, git_branch))
|
|
243
255
|
else:
|
|
244
256
|
# Search current project only
|
|
245
257
|
claude_dir = get_claude_project_dir(claude_home)
|
|
@@ -251,12 +263,12 @@ def find_sessions(keywords: List[str], global_search: bool = False, claude_home:
|
|
|
251
263
|
|
|
252
264
|
# Search all JSONL files in the directory
|
|
253
265
|
for jsonl_file in claude_dir.glob("*.jsonl"):
|
|
254
|
-
matches, line_count = search_keywords_in_file(jsonl_file, keywords)
|
|
266
|
+
matches, line_count, git_branch = search_keywords_in_file(jsonl_file, keywords)
|
|
255
267
|
if matches:
|
|
256
268
|
session_id = jsonl_file.stem
|
|
257
269
|
mod_time = jsonl_file.stat().st_mtime
|
|
258
270
|
preview = get_session_preview(jsonl_file)
|
|
259
|
-
matching_sessions.append((session_id, mod_time, line_count, project_name, preview, os.getcwd()))
|
|
271
|
+
matching_sessions.append((session_id, mod_time, line_count, project_name, preview, os.getcwd(), git_branch))
|
|
260
272
|
|
|
261
273
|
# Sort by modification time (newest first)
|
|
262
274
|
matching_sessions.sort(key=lambda x: x[1], reverse=True)
|
|
@@ -264,7 +276,7 @@ def find_sessions(keywords: List[str], global_search: bool = False, claude_home:
|
|
|
264
276
|
return matching_sessions
|
|
265
277
|
|
|
266
278
|
|
|
267
|
-
def display_interactive_ui(sessions: List[Tuple[str, float, int, str, str, str]], keywords: List[str], stderr_mode: bool = False, num_matches: int = 10) -> Optional[Tuple[str, str]]:
|
|
279
|
+
def display_interactive_ui(sessions: List[Tuple[str, float, int, str, str, str, Optional[str]]], keywords: List[str], stderr_mode: bool = False, num_matches: int = 10) -> Optional[Tuple[str, str]]:
|
|
268
280
|
"""Display interactive UI for session selection."""
|
|
269
281
|
if not RICH_AVAILABLE:
|
|
270
282
|
return None
|
|
@@ -292,16 +304,19 @@ def display_interactive_ui(sessions: List[Tuple[str, float, int, str, str, str]]
|
|
|
292
304
|
table.add_column("#", style="bold yellow", width=3)
|
|
293
305
|
table.add_column("Session ID", style="dim")
|
|
294
306
|
table.add_column("Project", style="green")
|
|
307
|
+
table.add_column("Branch", style="magenta")
|
|
295
308
|
table.add_column("Date", style="blue")
|
|
296
309
|
table.add_column("Lines", style="cyan", justify="right")
|
|
297
310
|
table.add_column("Preview", style="white", overflow="fold")
|
|
298
311
|
|
|
299
|
-
for idx, (session_id, mod_time, line_count, project_name, preview, _) in enumerate(display_sessions, 1):
|
|
312
|
+
for idx, (session_id, mod_time, line_count, project_name, preview, _, git_branch) in enumerate(display_sessions, 1):
|
|
300
313
|
mod_date = datetime.fromtimestamp(mod_time).strftime('%Y-%m-%d %H:%M')
|
|
314
|
+
branch_display = git_branch if git_branch else "N/A"
|
|
301
315
|
table.add_row(
|
|
302
316
|
str(idx),
|
|
303
317
|
session_id[:8] + "...",
|
|
304
318
|
project_name,
|
|
319
|
+
branch_display,
|
|
305
320
|
mod_date,
|
|
306
321
|
str(line_count),
|
|
307
322
|
preview
|
|
@@ -327,6 +342,11 @@ def display_interactive_ui(sessions: List[Tuple[str, float, int, str, str, str]]
|
|
|
327
342
|
console=ui_console
|
|
328
343
|
)
|
|
329
344
|
|
|
345
|
+
# Handle empty input
|
|
346
|
+
if not choice or not choice.strip():
|
|
347
|
+
ui_console.print("[red]Invalid choice. Please try again.[/red]")
|
|
348
|
+
continue
|
|
349
|
+
|
|
330
350
|
# Restore stdout
|
|
331
351
|
if stderr_mode:
|
|
332
352
|
sys.stdout.close()
|
|
@@ -340,7 +360,10 @@ def display_interactive_ui(sessions: List[Tuple[str, float, int, str, str, str]]
|
|
|
340
360
|
except KeyboardInterrupt:
|
|
341
361
|
ui_console.print("\n[yellow]Cancelled[/yellow]")
|
|
342
362
|
return None
|
|
343
|
-
except
|
|
363
|
+
except EOFError:
|
|
364
|
+
ui_console.print("\n[yellow]Cancelled (EOF)[/yellow]")
|
|
365
|
+
return None
|
|
366
|
+
except ValueError:
|
|
344
367
|
ui_console.print("[red]Invalid choice. Please try again.[/red]")
|
|
345
368
|
|
|
346
369
|
|
|
@@ -496,12 +519,13 @@ To persist directory changes when resuming sessions:
|
|
|
496
519
|
# Fallback: print session IDs as before
|
|
497
520
|
if not args.shell:
|
|
498
521
|
print("\nMatching sessions:")
|
|
499
|
-
for idx, (session_id, mod_time, line_count, project_name, preview, project_path) in enumerate(matching_sessions[:args.num_matches], 1):
|
|
522
|
+
for idx, (session_id, mod_time, line_count, project_name, preview, project_path, git_branch) in enumerate(matching_sessions[:args.num_matches], 1):
|
|
500
523
|
mod_date = datetime.fromtimestamp(mod_time).strftime('%Y-%m-%d %H:%M:%S')
|
|
524
|
+
branch_display = git_branch if git_branch else "N/A"
|
|
501
525
|
if getattr(args, 'global'):
|
|
502
|
-
print(f"{idx}. {session_id} | {project_name} | {mod_date} | {line_count} lines", file=sys.stderr if args.shell else sys.stdout)
|
|
526
|
+
print(f"{idx}. {session_id} | {project_name} | {branch_display} | {mod_date} | {line_count} lines", file=sys.stderr if args.shell else sys.stdout)
|
|
503
527
|
else:
|
|
504
|
-
print(f"{idx}. {session_id} | {mod_date} | {line_count} lines", file=sys.stderr if args.shell else sys.stdout)
|
|
528
|
+
print(f"{idx}. {session_id} | {branch_display} | {mod_date} | {line_count} lines", file=sys.stderr if args.shell else sys.stdout)
|
|
505
529
|
|
|
506
530
|
if len(matching_sessions) > args.num_matches:
|
|
507
531
|
print(f"\n... and {len(matching_sessions) - args.num_matches} more sessions", file=sys.stderr if args.shell else sys.stdout)
|
|
@@ -510,7 +534,7 @@ To persist directory changes when resuming sessions:
|
|
|
510
534
|
if len(matching_sessions) == 1:
|
|
511
535
|
if not args.shell:
|
|
512
536
|
print("\nOnly one match found. Resuming automatically...")
|
|
513
|
-
session_id, _, _, _, _, project_path = matching_sessions[0]
|
|
537
|
+
session_id, _, _, _, _, project_path, _ = matching_sessions[0]
|
|
514
538
|
resume_session(session_id, project_path, shell_mode=args.shell)
|
|
515
539
|
else:
|
|
516
540
|
try:
|
|
@@ -521,10 +545,15 @@ To persist directory changes when resuming sessions:
|
|
|
521
545
|
choice = sys.stdin.readline().strip()
|
|
522
546
|
else:
|
|
523
547
|
choice = input("\nEnter number to resume session (or Ctrl+C to cancel): ")
|
|
548
|
+
|
|
549
|
+
# Handle empty input or EOF
|
|
550
|
+
if not choice:
|
|
551
|
+
print("Cancelled (EOF)", file=sys.stderr)
|
|
552
|
+
sys.exit(0)
|
|
524
553
|
|
|
525
554
|
idx = int(choice) - 1
|
|
526
555
|
if 0 <= idx < min(args.num_matches, len(matching_sessions)):
|
|
527
|
-
session_id, _, _, _, _, project_path = matching_sessions[idx]
|
|
556
|
+
session_id, _, _, _, _, project_path, _ = matching_sessions[idx]
|
|
528
557
|
resume_session(session_id, project_path, shell_mode=args.shell)
|
|
529
558
|
else:
|
|
530
559
|
print("Invalid choice", file=sys.stderr)
|
{claude_code_tools-0.1.13 → claude_code_tools-0.1.17}/claude_code_tools/tmux_cli_controller.py
RENAMED
|
@@ -128,6 +128,19 @@ class TmuxCLIController:
|
|
|
128
128
|
output, code = self._run_tmux_command(['display-message', '-p', '#{pane_id}'])
|
|
129
129
|
return output if code == 0 else None
|
|
130
130
|
|
|
131
|
+
def get_current_window_id(self) -> Optional[str]:
|
|
132
|
+
"""Get the ID of the current tmux window."""
|
|
133
|
+
# Use TMUX_PANE environment variable to get the pane we're running in
|
|
134
|
+
import os
|
|
135
|
+
current_pane = os.environ.get('TMUX_PANE')
|
|
136
|
+
if current_pane:
|
|
137
|
+
# Get the window ID for this specific pane
|
|
138
|
+
output, code = self._run_tmux_command(['display-message', '-t', current_pane, '-p', '#{window_id}'])
|
|
139
|
+
return output if code == 0 else None
|
|
140
|
+
# Fallback to current window
|
|
141
|
+
output, code = self._run_tmux_command(['display-message', '-p', '#{window_id}'])
|
|
142
|
+
return output if code == 0 else None
|
|
143
|
+
|
|
131
144
|
def list_panes(self) -> List[Dict[str, str]]:
|
|
132
145
|
"""
|
|
133
146
|
List all panes in the current window.
|
|
@@ -175,8 +188,15 @@ class TmuxCLIController:
|
|
|
175
188
|
Returns:
|
|
176
189
|
Pane ID of the created pane
|
|
177
190
|
"""
|
|
191
|
+
# Get the current window ID to ensure pane is created in this window
|
|
192
|
+
current_window_id = self.get_current_window_id()
|
|
193
|
+
|
|
178
194
|
cmd = ['split-window']
|
|
179
195
|
|
|
196
|
+
# Target the specific window where tmux-cli was called from
|
|
197
|
+
if current_window_id:
|
|
198
|
+
cmd.extend(['-t', current_window_id])
|
|
199
|
+
|
|
180
200
|
if vertical:
|
|
181
201
|
cmd.append('-h')
|
|
182
202
|
else:
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
# Zsh Shell Setup Guide
|
|
2
|
+
|
|
3
|
+
This guide covers the essential setup for a productive Zsh environment with
|
|
4
|
+
Oh My Zsh, plugins, and Starship prompt.
|
|
5
|
+
|
|
6
|
+
## Prerequisites
|
|
7
|
+
|
|
8
|
+
1. **Install Homebrew** (macOS package manager)
|
|
9
|
+
```bash
|
|
10
|
+
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
2. **Install Zsh** (usually pre-installed on macOS)
|
|
14
|
+
```bash
|
|
15
|
+
brew install zsh
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Oh My Zsh Installation
|
|
19
|
+
|
|
20
|
+
Install Oh My Zsh framework for managing Zsh configuration:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
This creates `~/.oh-my-zsh` directory and a basic `~/.zshrc` file.
|
|
27
|
+
|
|
28
|
+
## Essential Plugins
|
|
29
|
+
|
|
30
|
+
### 1. Zsh Autosuggestions
|
|
31
|
+
|
|
32
|
+
Suggests commands as you type based on history and completions:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
git clone https://github.com/zsh-users/zsh-autosuggestions ~/.zsh/zsh-autosuggestions
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 2. Zsh Syntax Highlighting
|
|
39
|
+
|
|
40
|
+
Provides syntax highlighting for the shell:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 3. Configure Plugins in .zshrc
|
|
47
|
+
|
|
48
|
+
Add to your `~/.zshrc`:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# Oh My Zsh path
|
|
52
|
+
export ZSH="$HOME/.oh-my-zsh"
|
|
53
|
+
|
|
54
|
+
# Plugins configuration
|
|
55
|
+
plugins=(
|
|
56
|
+
git
|
|
57
|
+
zsh-autosuggestions
|
|
58
|
+
zsh-syntax-highlighting
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Source Oh My Zsh (commented out if using Starship)
|
|
62
|
+
# source $ZSH/oh-my-zsh.sh
|
|
63
|
+
|
|
64
|
+
# Source autosuggestions manually if needed
|
|
65
|
+
source ~/.zsh/zsh-autosuggestions/zsh-autosuggestions.zsh
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Starship Prompt
|
|
69
|
+
|
|
70
|
+
Starship is a minimal, blazing-fast, and customizable prompt for any shell.
|
|
71
|
+
|
|
72
|
+
### Installation
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
curl -sS https://starship.rs/install.sh | sh
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Configuration
|
|
79
|
+
|
|
80
|
+
Add to the end of your `~/.zshrc`:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
eval "$(starship init zsh)"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Starship Configuration File
|
|
87
|
+
|
|
88
|
+
You can customize starship by creating/editing `~/.config/starship.toml`.
|
|
89
|
+
|
|
90
|
+
I recommend this `catppuccin` preset:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
starship preset catppuccin-powerline -o ~/.config/starship.toml
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
But if you want to tweak it, see the starship config docs.
|
|
97
|
+
https://starship.rs/guide/
|
|
98
|
+
|
|
99
|
+
## Enhanced Directory Listings with eza
|
|
100
|
+
|
|
101
|
+
Eza is a modern replacement for `ls` with colors, icons, and Git integration.
|
|
102
|
+
|
|
103
|
+
### Installation
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
brew install eza
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Configuration
|
|
110
|
+
|
|
111
|
+
Add these aliases to your `~/.zshrc` to replace default `ls` commands:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# Replace ls with eza for beautiful directory listings
|
|
115
|
+
alias ls='eza --icons --group-directories-first'
|
|
116
|
+
alias ll='eza -l --icons --group-directories-first --header'
|
|
117
|
+
alias la='eza -la --icons --group-directories-first --header'
|
|
118
|
+
alias lt='eza --tree --icons --level=2'
|
|
119
|
+
alias ltd='eza --tree --icons --level=2 --only-dirs'
|
|
120
|
+
|
|
121
|
+
# Extended eza aliases
|
|
122
|
+
alias l='eza -lbF --git --icons' # list with git status
|
|
123
|
+
alias llm='eza -lbGd --git --sort=modified' # long list, modified date sort
|
|
124
|
+
alias lls='eza -lbhHigmuSa --time-style=long-iso --git --color-scale' # full details
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Features
|
|
128
|
+
|
|
129
|
+
- **Icons**: Shows file type icons (requires a Nerd Font)
|
|
130
|
+
- **Colors**: Color-codes files by type and permissions
|
|
131
|
+
- **Git Integration**: Shows git status in listings
|
|
132
|
+
- **Tree View**: Built-in tree view with `--tree` flag
|
|
133
|
+
- **Sorting**: Groups directories first by default
|
|
134
|
+
|
|
135
|
+
### Usage Examples
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
ls # Basic listing with icons
|
|
139
|
+
ll # Long format with details
|
|
140
|
+
la # Show hidden files
|
|
141
|
+
lt # Tree view (2 levels)
|
|
142
|
+
l # List with git status indicators
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Terminal Title Management
|
|
146
|
+
|
|
147
|
+
Add custom terminal title that shows current directory:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
# Disable auto-setting terminal title
|
|
151
|
+
DISABLE_AUTO_TITLE="true"
|
|
152
|
+
|
|
153
|
+
# Set terminal title to current directory
|
|
154
|
+
function precmd () {
|
|
155
|
+
echo -ne "\033]0;$(print -rD $PWD)\007"
|
|
156
|
+
}
|
|
157
|
+
precmd
|
|
158
|
+
|
|
159
|
+
# Show command being executed in title with rocket emojis
|
|
160
|
+
function preexec () {
|
|
161
|
+
print -Pn "\e]0;🚀 $(print -rD $PWD) $1 🚀\a"
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## History Search Enhancement
|
|
166
|
+
|
|
167
|
+
### Option 1: Atuin (Modern Shell History)
|
|
168
|
+
|
|
169
|
+
Install Atuin for enhanced history search:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
brew install atuin
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Configure in `~/.zshrc`:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
export ATUIN_NOBIND="true"
|
|
179
|
+
eval "$(atuin init zsh)"
|
|
180
|
+
# Bind to up arrow keys (depends on terminal)
|
|
181
|
+
bindkey '^[[A' _atuin_search_widget
|
|
182
|
+
bindkey '^[OA' _atuin_search_widget
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Option 2: HSTR (Simple History Search)
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
brew install hstr
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Configure in `~/.zshrc`:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
alias hh=hstr
|
|
195
|
+
setopt histignorespace # skip cmds w/ leading space from history
|
|
196
|
+
export HSTR_CONFIG=hicolor # get more colors
|
|
197
|
+
bindkey -s "\C-r" "\C-a hstr -- \C-j" # bind hstr to Ctrl-r
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Useful Aliases
|
|
201
|
+
|
|
202
|
+
Add these productivity aliases to your `~/.zshrc`:
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
# Git aliases
|
|
206
|
+
alias gsuno="git status -uno" # git status without untracked files
|
|
207
|
+
alias gspu="git stash push -m" # stash with message
|
|
208
|
+
alias gspop="git stash pop" # pop stash
|
|
209
|
+
alias gsl="git stash list" # list stashes
|
|
210
|
+
|
|
211
|
+
# Quick git commit amend and push
|
|
212
|
+
function gcpq() {
|
|
213
|
+
ga -u
|
|
214
|
+
git commit --amend --no-edit
|
|
215
|
+
git push origin main --force-with-lease
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
# Tmux aliases
|
|
219
|
+
alias tmnew="tmux new -s "
|
|
220
|
+
alias tmls="tmux ls"
|
|
221
|
+
alias tma="tmux a -t "
|
|
222
|
+
alias tmk="tmux kill-session -t "
|
|
223
|
+
alias tmd="tmux detach"
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Completion System
|
|
227
|
+
|
|
228
|
+
Enable advanced tab completion:
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
autoload -Uz compinit
|
|
232
|
+
zstyle ':completion:*' menu select
|
|
233
|
+
fpath+=~/.zfunc
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Environment Variables
|
|
237
|
+
|
|
238
|
+
Set common environment variables:
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
export EDITOR="nano" # or "vim", "code", etc.
|
|
242
|
+
export TERM=xterm-256color
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Final .zshrc Structure
|
|
246
|
+
|
|
247
|
+
Your `~/.zshrc` should follow this general structure:
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
# 1. Oh My Zsh configuration
|
|
251
|
+
export ZSH="$HOME/.oh-my-zsh"
|
|
252
|
+
plugins=(git zsh-autosuggestions zsh-syntax-highlighting)
|
|
253
|
+
|
|
254
|
+
# 2. Source additional files
|
|
255
|
+
source ~/.zsh/zsh-autosuggestions/zsh-autosuggestions.zsh
|
|
256
|
+
|
|
257
|
+
# 3. Environment variables
|
|
258
|
+
export EDITOR="nano"
|
|
259
|
+
export TERM=xterm-256color
|
|
260
|
+
|
|
261
|
+
# 4. Aliases and functions
|
|
262
|
+
alias gsuno="git status -uno"
|
|
263
|
+
# ... more aliases
|
|
264
|
+
|
|
265
|
+
# 5. Terminal title customization
|
|
266
|
+
DISABLE_AUTO_TITLE="true"
|
|
267
|
+
function precmd () { ... }
|
|
268
|
+
function preexec () { ... }
|
|
269
|
+
|
|
270
|
+
# 6. History search tool (Atuin or HSTR)
|
|
271
|
+
eval "$(atuin init zsh)"
|
|
272
|
+
|
|
273
|
+
# 7. Completion system
|
|
274
|
+
autoload -Uz compinit
|
|
275
|
+
zstyle ':completion:*' menu select
|
|
276
|
+
|
|
277
|
+
# 8. Starship prompt (at the very end)
|
|
278
|
+
eval "$(starship init zsh)"
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Verification
|
|
282
|
+
|
|
283
|
+
After setup, verify everything works:
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
# Reload shell configuration
|
|
287
|
+
source ~/.zshrc
|
|
288
|
+
|
|
289
|
+
# Test autosuggestions (type a partial command and see suggestions)
|
|
290
|
+
# Test syntax highlighting (commands should be colored)
|
|
291
|
+
# Test Starship prompt (should see a styled prompt)
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Troubleshooting
|
|
295
|
+
|
|
296
|
+
1. **Slow shell startup**: Comment out unused plugins and features
|
|
297
|
+
2. **Autosuggestions not working**: Ensure the plugin is properly cloned and
|
|
298
|
+
sourced
|
|
299
|
+
3. **Starship not showing**: Make sure it's the last line in `.zshrc`
|
|
300
|
+
4. **Terminal title not updating**: Check if `DISABLE_AUTO_TITLE="true"` is set
|
|
301
|
+
|
|
302
|
+
## Additional Resources
|
|
303
|
+
|
|
304
|
+
- [Oh My Zsh Documentation](https://github.com/ohmyzsh/ohmyzsh/wiki)
|
|
305
|
+
- [Starship Documentation](https://starship.rs/config/)
|
|
306
|
+
- [Zsh Autosuggestions](https://github.com/zsh-users/zsh-autosuggestions)
|
|
307
|
+
- [Atuin Documentation](https://github.com/atuinsh/atuin)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "claude-code-tools"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.17"
|
|
4
4
|
description = "Collection of tools for working with Claude Code"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -17,6 +17,7 @@ dev = ["commitizen>=3.0.0"]
|
|
|
17
17
|
find-claude-session = "claude_code_tools.find_claude_session:main"
|
|
18
18
|
vault = "claude_code_tools.dotenv_vault:main"
|
|
19
19
|
tmux-cli = "claude_code_tools.tmux_cli_controller:main"
|
|
20
|
+
env-safe = "claude_code_tools.env_safe:main"
|
|
20
21
|
|
|
21
22
|
[build-system]
|
|
22
23
|
requires = ["hatchling"]
|
|
@@ -40,7 +41,7 @@ exclude = [
|
|
|
40
41
|
|
|
41
42
|
[tool.commitizen]
|
|
42
43
|
name = "cz_conventional_commits"
|
|
43
|
-
version = "0.1.
|
|
44
|
+
version = "0.1.17"
|
|
44
45
|
tag_format = "v$version"
|
|
45
46
|
version_files = [
|
|
46
47
|
"pyproject.toml:version",
|
|
File without changes
|
|
File without changes
|
{claude_code_tools-0.1.13 → claude_code_tools-0.1.17}/claude_code_tools/tmux_remote_controller.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|