claude-code-tools 0.1.20__tar.gz → 0.2.0__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.

Files changed (22) hide show
  1. {claude_code_tools-0.1.20 → claude_code_tools-0.2.0}/PKG-INFO +127 -6
  2. {claude_code_tools-0.1.20 → claude_code_tools-0.2.0}/README.md +126 -5
  3. {claude_code_tools-0.1.20 → claude_code_tools-0.2.0}/claude_code_tools/__init__.py +1 -1
  4. {claude_code_tools-0.1.20 → claude_code_tools-0.2.0}/claude_code_tools/find_claude_session.py +36 -15
  5. claude_code_tools-0.2.0/claude_code_tools/find_codex_session.py +458 -0
  6. claude_code_tools-0.2.0/docs/lmsh.md +48 -0
  7. {claude_code_tools-0.1.20 → claude_code_tools-0.2.0}/pyproject.toml +3 -2
  8. {claude_code_tools-0.1.20 → claude_code_tools-0.2.0}/.gitignore +0 -0
  9. {claude_code_tools-0.1.20 → claude_code_tools-0.2.0}/LICENSE +0 -0
  10. {claude_code_tools-0.1.20 → claude_code_tools-0.2.0}/claude_code_tools/codex_bridge_mcp.py +0 -0
  11. {claude_code_tools-0.1.20 → claude_code_tools-0.2.0}/claude_code_tools/dotenv_vault.py +0 -0
  12. {claude_code_tools-0.1.20 → claude_code_tools-0.2.0}/claude_code_tools/env_safe.py +0 -0
  13. {claude_code_tools-0.1.20 → claude_code_tools-0.2.0}/claude_code_tools/tmux_cli_controller.py +0 -0
  14. {claude_code_tools-0.1.20 → claude_code_tools-0.2.0}/claude_code_tools/tmux_remote_controller.py +0 -0
  15. {claude_code_tools-0.1.20 → claude_code_tools-0.2.0}/docs/cc-codex-instructions.md +0 -0
  16. {claude_code_tools-0.1.20 → claude_code_tools-0.2.0}/docs/claude-code-chutes.md +0 -0
  17. {claude_code_tools-0.1.20 → claude_code_tools-0.2.0}/docs/claude-code-tmux-tutorials.md +0 -0
  18. {claude_code_tools-0.1.20 → claude_code_tools-0.2.0}/docs/dot-zshrc.md +0 -0
  19. {claude_code_tools-0.1.20 → claude_code_tools-0.2.0}/docs/find-claude-session.md +0 -0
  20. {claude_code_tools-0.1.20 → claude_code_tools-0.2.0}/docs/reddit-post.md +0 -0
  21. {claude_code_tools-0.1.20 → claude_code_tools-0.2.0}/docs/tmux-cli-instructions.md +0 -0
  22. {claude_code_tools-0.1.20 → claude_code_tools-0.2.0}/docs/vault-documentation.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-code-tools
3
- Version: 0.1.20
3
+ Version: 0.2.0
4
4
  Summary: Collection of tools for working with Claude Code
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.11
@@ -17,6 +17,24 @@ Description-Content-Type: text/markdown
17
17
  A collection of practical tools, hooks, and utilities for enhancing Claude Code
18
18
  and other CLI coding agents.
19
19
 
20
+ ## Table of Contents
21
+
22
+ - [🎮 tmux-cli: Bridging Claude Code and Interactive CLIs — "playwright for the terminal"](#tmux-cli-bridging-claude-code-and-interactive-clis)
23
+ - [🚀 Quick Start](#quick-start)
24
+ - [🎮 tmux-cli Deep Dive](#tmux-cli-deep-dive)
25
+ - [🚀 lmsh (Experimental) — natural language to shell commands](#lmsh-experimental)
26
+ - [🔍 find-claude-session — search and resume Claude sessions](#find-claude-session)
27
+ - [🔍 find-codex-session — search and resume Codex sessions](#find-codex-session)
28
+ - [🔐 vault — encrypted .env backup & sync](#vault)
29
+ - [🔍 env-safe — inspect .env safely without values](#env-safe)
30
+ - [🛡️ Claude Code Safety Hooks — guardrails for bash, git, env, files](#claude-code-safety-hooks)
31
+ - [🤖 Using Claude Code with Open-weight Anthropic API-compatible LLM Providers](#using-claude-code-with-open-weight-anthropic-api-compatible-llm-providers)
32
+ - [📚 Documentation](#documentation)
33
+ - [📋 Requirements](#requirements)
34
+ - [🛠️ Development](#development)
35
+ - [📄 License](#license)
36
+
37
+ <a id="tmux-cli-bridging-claude-code-and-interactive-clis"></a>
20
38
  ## 🎮 tmux-cli: Bridging Claude Code and Interactive CLIs
21
39
 
22
40
  > **Note**: While the description below focuses on Claude Code, tmux-cli works with any CLI coding agent.
@@ -50,6 +68,7 @@ use tmux-cli behind the scenes.
50
68
 
51
69
  **Works anywhere**: Automatically handles both local tmux panes and remote sessions.
52
70
 
71
+ <a id="quick-start"></a>
53
72
  ## 🚀 Quick Start
54
73
 
55
74
  ```bash
@@ -63,9 +82,11 @@ uv tool install git+https://github.com/pchalasani/claude-code-tools
63
82
  This gives you:
64
83
  - `tmux-cli` - The interactive CLI controller we just covered
65
84
  - `find-claude-session` - Search and resume Claude Code sessions by keywords
85
+ - `find-codex-session` - Search and resume Codex sessions by keywords
66
86
  - `vault` - Encrypted backup for your .env files
67
87
  - `env-safe` - Safely inspect .env files without exposing values
68
88
 
89
+ <a id="tmux-cli-deep-dive"></a>
69
90
  ## 🎮 tmux-cli Deep Dive
70
91
 
71
92
  ### What Claude Code Can Do With tmux-cli
@@ -117,6 +138,44 @@ claude mcp add puppeteer -- npx -y @modelcontextprotocol/server-puppeteer
117
138
 
118
139
  For detailed instructions, see [docs/tmux-cli-instructions.md](docs/tmux-cli-instructions.md).
119
140
 
141
+ <a id="lmsh-experimental"></a>
142
+ ## 🚀 lmsh (Experimental)
143
+
144
+ Natural language shell - type what you want in plain English, get an editable command.
145
+
146
+ ```bash
147
+ # Direct usage - translate, edit, execute, then enter interactive mode
148
+ $ lmsh "show me all python files modified today"
149
+ find . -name "*.py" -mtime 0 # <-- Edit before running
150
+
151
+ # Or interactive mode
152
+ $ lmsh
153
+ lmsh> show recent docker containers
154
+ docker ps -n 5 # <-- Edit before running
155
+ ```
156
+
157
+ **Features:**
158
+ - Rust-based for instant startup (<1ms binary load time)
159
+ - Translates natural language to shell commands using Claude Code CLI
160
+ - Commands are editable before execution - full control
161
+ - Preserves your shell environment
162
+
163
+ **Note:** Requires Claude Code CLI (`claude` command) to be installed. The translation adds ~2-3s due to Claude Code CLI startup.
164
+
165
+ **Installation:**
166
+ ```bash
167
+ # Install from crates.io (easiest, requires Rust)
168
+ cargo install lmsh
169
+
170
+ # Or build from source
171
+ cd lmsh && cargo build --release
172
+ cp target/release/lmsh ~/.cargo/bin/
173
+ # Or: make lmsh-install
174
+ ```
175
+
176
+ See [docs/lmsh.md](docs/lmsh.md) for details.
177
+
178
+ <a id="find-claude-session"></a>
120
179
  ## 🔍 find-claude-session
121
180
 
122
181
  Search and resume Claude Code sessions by keywords with an interactive UI.
@@ -170,6 +229,46 @@ Looks like this --
170
229
 
171
230
  ![fcs.png](docs/fcs.png)
172
231
 
232
+ <a id="find-codex-session"></a>
233
+ ## 🔍 find-codex-session
234
+
235
+ Search and resume Codex sessions by keywords. Usage is similar to `find-claude-session` above, but works with Codex session files instead.
236
+
237
+ ### Key Differences from find-claude-session
238
+
239
+ - Searches `~/.codex/sessions/` (organized by YYYY/MM/DD directories)
240
+ - Extracts metadata from `session_meta` entries in Codex JSONL files
241
+ - Resumes sessions with `codex resume <session-id>`
242
+
243
+ ### Usage
244
+
245
+ ```bash
246
+ # Search in current project only (default)
247
+ find-codex-session "keyword1,keyword2"
248
+
249
+ # Search across all projects
250
+ find-codex-session "keywords" -g
251
+ find-codex-session "keywords" --global
252
+
253
+ # Limit number of results
254
+ find-codex-session "keywords" -n 5
255
+
256
+ # Custom Codex home directory
257
+ find-codex-session "keywords" --codex-home /custom/path
258
+ ```
259
+
260
+ ### Features
261
+
262
+ - **Project filtering**: Search current project only (default) or all projects with `-g`
263
+ - Case-insensitive AND keyword search across all session content
264
+ - Interactive session selection with Rich table display
265
+ - Shows project name, git branch, date, line count, and preview of last user message
266
+ - Automatic session resumption with `codex resume`
267
+ - Cross-project session support with directory change prompts
268
+ - Reverse chronological ordering (most recent first)
269
+ - Multi-line preview wrapping for better readability
270
+
271
+ <a id="vault"></a>
173
272
  ## 🔐 vault
174
273
 
175
274
  Centralized encrypted backup for .env files across all your projects using SOPS.
@@ -191,6 +290,7 @@ vault status # Check sync status for current project
191
290
 
192
291
  For detailed documentation, see [docs/vault-documentation.md](docs/vault-documentation.md).
193
292
 
293
+ <a id="env-safe"></a>
194
294
  ## 🔍 env-safe
195
295
 
196
296
  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.
@@ -216,6 +316,7 @@ env-safe --help # See all options
216
316
 
217
317
  Claude Code is completely blocked from directly accessing .env files - no reading, writing, or editing allowed. This prevents both accidental exposure of API keys and unintended modifications. The `env-safe` command provides the only approved way for Claude Code to inspect environment configuration safely, while any modifications must be done manually outside of Claude Code.
218
318
 
319
+ <a id="claude-code-safety-hooks"></a>
219
320
  ## 🛡️ Claude Code Safety Hooks
220
321
 
221
322
  This repository includes a comprehensive set of safety hooks that enhance Claude
@@ -225,8 +326,11 @@ Code's behavior and prevent dangerous operations.
225
326
 
226
327
  - **File Deletion Protection** - Blocks `rm` commands, enforces TRASH directory
227
328
  pattern
228
- - **Git Safety** - Prevents dangerous `git add -A`, unsafe checkouts, and
229
- accidental data loss
329
+ - **Git Safety** - Advanced git add protection with:
330
+ - Hard blocks: `git add .`, `git add ../`, `git add *`, `git add -A/--all`
331
+ - Speed bumps: Shows files before staging directories (e.g., `git add src/`)
332
+ - Commit speed bump: Warns on first attempt, allows on second
333
+ - Prevents unsafe checkouts and accidental data loss
230
334
  - **Environment Security** - Blocks all .env file operations (read/write/edit),
231
335
  suggests `env-safe` command for safe inspection
232
336
  - **Context Management** - Blocks reading files >500 lines to prevent context
@@ -263,10 +367,12 @@ Code's behavior and prevent dangerous operations.
263
367
 
264
368
  For complete documentation, see [hooks/README.md](hooks/README.md).
265
369
 
370
+ <a id="using-claude-code-with-open-weight-anthropic-api-compatible-llm-providers"></a>
266
371
  ## 🤖 Using Claude Code with Open-weight Anthropic API-compatible LLM Providers
267
372
 
268
373
  You can use Claude Code with alternative LLMs served via Anthropic-compatible
269
- APIs. Add these functions to your shell config (.bashrc/.zshrc):
374
+ APIs, e.g. Kimi-k2, GLM4.5 (from zai), Deepseek-v3.1.
375
+ Add these functions to your shell config (.bashrc/.zshrc):
270
376
 
271
377
  ```bash
272
378
  kimi() {
@@ -284,18 +390,30 @@ zai() {
284
390
  claude "$@"
285
391
  )
286
392
  }
393
+
394
+ dseek() {
395
+ (
396
+ export ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic
397
+ export ANTHROPIC_AUTH_TOKEN=${DEEPSEEK_API_KEY}
398
+ export ANTHROPIC_MODEL=deepseek-chat
399
+ export ANTHROPIC_SMALL_FAST_MODEL=deepseek-chat
400
+ claude "$@"
401
+ )
402
+ }
287
403
  ```
288
404
 
289
405
  After adding these functions:
290
- - Set your API keys: `export KIMI_API_KEY=your-kimi-key` and
291
- `export Z_API_KEY=your-z-key`
406
+ - Set your API keys: `export KIMI_API_KEY=your-kimi-key`,
407
+ `export Z_API_KEY=your-z-key`, `export DEEPSEEK_API_KEY=your-deepseek-key`
292
408
  - Run `kimi` to use Claude Code with the Kimi K2 LLM
293
409
  - Run `zai` to use Claude Code with the GLM-4.5 model
410
+ - Run `dseek` to use Claude Code with the DeepSeek model
294
411
 
295
412
  The functions use subshells to ensure the environment variables don't affect
296
413
  your main shell session, so you could be running multiple instances of Claude Code,
297
414
  each using a different LLM.
298
415
 
416
+ <a id="documentation"></a>
299
417
  ## 📚 Documentation
300
418
 
301
419
  - [tmux-cli detailed instructions](docs/tmux-cli-instructions.md) -
@@ -306,6 +424,7 @@ each using a different LLM.
306
424
  Complete guide for the .env backup system
307
425
  - [Hook configuration](hooks/README.md) - Setting up Claude Code hooks
308
426
 
427
+ <a id="requirements"></a>
309
428
  ## 📋 Requirements
310
429
 
311
430
  - Python 3.11+
@@ -313,6 +432,7 @@ each using a different LLM.
313
432
  - tmux (for tmux-cli functionality)
314
433
  - SOPS (for vault functionality)
315
434
 
435
+ <a id="development"></a>
316
436
  ## 🛠️ Development
317
437
 
318
438
  ### Setup
@@ -369,6 +489,7 @@ Run `make help` to see all available commands:
369
489
  - `make release` - Bump patch version and install globally
370
490
  - `make patch/minor/major` - Version bump commands
371
491
 
492
+ <a id="license"></a>
372
493
  ## 📄 License
373
494
 
374
495
  MIT
@@ -3,6 +3,24 @@
3
3
  A collection of practical tools, hooks, and utilities for enhancing Claude Code
4
4
  and other CLI coding agents.
5
5
 
6
+ ## Table of Contents
7
+
8
+ - [🎮 tmux-cli: Bridging Claude Code and Interactive CLIs — "playwright for the terminal"](#tmux-cli-bridging-claude-code-and-interactive-clis)
9
+ - [🚀 Quick Start](#quick-start)
10
+ - [🎮 tmux-cli Deep Dive](#tmux-cli-deep-dive)
11
+ - [🚀 lmsh (Experimental) — natural language to shell commands](#lmsh-experimental)
12
+ - [🔍 find-claude-session — search and resume Claude sessions](#find-claude-session)
13
+ - [🔍 find-codex-session — search and resume Codex sessions](#find-codex-session)
14
+ - [🔐 vault — encrypted .env backup & sync](#vault)
15
+ - [🔍 env-safe — inspect .env safely without values](#env-safe)
16
+ - [🛡️ Claude Code Safety Hooks — guardrails for bash, git, env, files](#claude-code-safety-hooks)
17
+ - [🤖 Using Claude Code with Open-weight Anthropic API-compatible LLM Providers](#using-claude-code-with-open-weight-anthropic-api-compatible-llm-providers)
18
+ - [📚 Documentation](#documentation)
19
+ - [📋 Requirements](#requirements)
20
+ - [🛠️ Development](#development)
21
+ - [📄 License](#license)
22
+
23
+ <a id="tmux-cli-bridging-claude-code-and-interactive-clis"></a>
6
24
  ## 🎮 tmux-cli: Bridging Claude Code and Interactive CLIs
7
25
 
8
26
  > **Note**: While the description below focuses on Claude Code, tmux-cli works with any CLI coding agent.
@@ -36,6 +54,7 @@ use tmux-cli behind the scenes.
36
54
 
37
55
  **Works anywhere**: Automatically handles both local tmux panes and remote sessions.
38
56
 
57
+ <a id="quick-start"></a>
39
58
  ## 🚀 Quick Start
40
59
 
41
60
  ```bash
@@ -49,9 +68,11 @@ uv tool install git+https://github.com/pchalasani/claude-code-tools
49
68
  This gives you:
50
69
  - `tmux-cli` - The interactive CLI controller we just covered
51
70
  - `find-claude-session` - Search and resume Claude Code sessions by keywords
71
+ - `find-codex-session` - Search and resume Codex sessions by keywords
52
72
  - `vault` - Encrypted backup for your .env files
53
73
  - `env-safe` - Safely inspect .env files without exposing values
54
74
 
75
+ <a id="tmux-cli-deep-dive"></a>
55
76
  ## 🎮 tmux-cli Deep Dive
56
77
 
57
78
  ### What Claude Code Can Do With tmux-cli
@@ -103,6 +124,44 @@ claude mcp add puppeteer -- npx -y @modelcontextprotocol/server-puppeteer
103
124
 
104
125
  For detailed instructions, see [docs/tmux-cli-instructions.md](docs/tmux-cli-instructions.md).
105
126
 
127
+ <a id="lmsh-experimental"></a>
128
+ ## 🚀 lmsh (Experimental)
129
+
130
+ Natural language shell - type what you want in plain English, get an editable command.
131
+
132
+ ```bash
133
+ # Direct usage - translate, edit, execute, then enter interactive mode
134
+ $ lmsh "show me all python files modified today"
135
+ find . -name "*.py" -mtime 0 # <-- Edit before running
136
+
137
+ # Or interactive mode
138
+ $ lmsh
139
+ lmsh> show recent docker containers
140
+ docker ps -n 5 # <-- Edit before running
141
+ ```
142
+
143
+ **Features:**
144
+ - Rust-based for instant startup (<1ms binary load time)
145
+ - Translates natural language to shell commands using Claude Code CLI
146
+ - Commands are editable before execution - full control
147
+ - Preserves your shell environment
148
+
149
+ **Note:** Requires Claude Code CLI (`claude` command) to be installed. The translation adds ~2-3s due to Claude Code CLI startup.
150
+
151
+ **Installation:**
152
+ ```bash
153
+ # Install from crates.io (easiest, requires Rust)
154
+ cargo install lmsh
155
+
156
+ # Or build from source
157
+ cd lmsh && cargo build --release
158
+ cp target/release/lmsh ~/.cargo/bin/
159
+ # Or: make lmsh-install
160
+ ```
161
+
162
+ See [docs/lmsh.md](docs/lmsh.md) for details.
163
+
164
+ <a id="find-claude-session"></a>
106
165
  ## 🔍 find-claude-session
107
166
 
108
167
  Search and resume Claude Code sessions by keywords with an interactive UI.
@@ -156,6 +215,46 @@ Looks like this --
156
215
 
157
216
  ![fcs.png](docs/fcs.png)
158
217
 
218
+ <a id="find-codex-session"></a>
219
+ ## 🔍 find-codex-session
220
+
221
+ Search and resume Codex sessions by keywords. Usage is similar to `find-claude-session` above, but works with Codex session files instead.
222
+
223
+ ### Key Differences from find-claude-session
224
+
225
+ - Searches `~/.codex/sessions/` (organized by YYYY/MM/DD directories)
226
+ - Extracts metadata from `session_meta` entries in Codex JSONL files
227
+ - Resumes sessions with `codex resume <session-id>`
228
+
229
+ ### Usage
230
+
231
+ ```bash
232
+ # Search in current project only (default)
233
+ find-codex-session "keyword1,keyword2"
234
+
235
+ # Search across all projects
236
+ find-codex-session "keywords" -g
237
+ find-codex-session "keywords" --global
238
+
239
+ # Limit number of results
240
+ find-codex-session "keywords" -n 5
241
+
242
+ # Custom Codex home directory
243
+ find-codex-session "keywords" --codex-home /custom/path
244
+ ```
245
+
246
+ ### Features
247
+
248
+ - **Project filtering**: Search current project only (default) or all projects with `-g`
249
+ - Case-insensitive AND keyword search across all session content
250
+ - Interactive session selection with Rich table display
251
+ - Shows project name, git branch, date, line count, and preview of last user message
252
+ - Automatic session resumption with `codex resume`
253
+ - Cross-project session support with directory change prompts
254
+ - Reverse chronological ordering (most recent first)
255
+ - Multi-line preview wrapping for better readability
256
+
257
+ <a id="vault"></a>
159
258
  ## 🔐 vault
160
259
 
161
260
  Centralized encrypted backup for .env files across all your projects using SOPS.
@@ -177,6 +276,7 @@ vault status # Check sync status for current project
177
276
 
178
277
  For detailed documentation, see [docs/vault-documentation.md](docs/vault-documentation.md).
179
278
 
279
+ <a id="env-safe"></a>
180
280
  ## 🔍 env-safe
181
281
 
182
282
  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.
@@ -202,6 +302,7 @@ env-safe --help # See all options
202
302
 
203
303
  Claude Code is completely blocked from directly accessing .env files - no reading, writing, or editing allowed. This prevents both accidental exposure of API keys and unintended modifications. The `env-safe` command provides the only approved way for Claude Code to inspect environment configuration safely, while any modifications must be done manually outside of Claude Code.
204
304
 
305
+ <a id="claude-code-safety-hooks"></a>
205
306
  ## 🛡️ Claude Code Safety Hooks
206
307
 
207
308
  This repository includes a comprehensive set of safety hooks that enhance Claude
@@ -211,8 +312,11 @@ Code's behavior and prevent dangerous operations.
211
312
 
212
313
  - **File Deletion Protection** - Blocks `rm` commands, enforces TRASH directory
213
314
  pattern
214
- - **Git Safety** - Prevents dangerous `git add -A`, unsafe checkouts, and
215
- accidental data loss
315
+ - **Git Safety** - Advanced git add protection with:
316
+ - Hard blocks: `git add .`, `git add ../`, `git add *`, `git add -A/--all`
317
+ - Speed bumps: Shows files before staging directories (e.g., `git add src/`)
318
+ - Commit speed bump: Warns on first attempt, allows on second
319
+ - Prevents unsafe checkouts and accidental data loss
216
320
  - **Environment Security** - Blocks all .env file operations (read/write/edit),
217
321
  suggests `env-safe` command for safe inspection
218
322
  - **Context Management** - Blocks reading files >500 lines to prevent context
@@ -249,10 +353,12 @@ Code's behavior and prevent dangerous operations.
249
353
 
250
354
  For complete documentation, see [hooks/README.md](hooks/README.md).
251
355
 
356
+ <a id="using-claude-code-with-open-weight-anthropic-api-compatible-llm-providers"></a>
252
357
  ## 🤖 Using Claude Code with Open-weight Anthropic API-compatible LLM Providers
253
358
 
254
359
  You can use Claude Code with alternative LLMs served via Anthropic-compatible
255
- APIs. Add these functions to your shell config (.bashrc/.zshrc):
360
+ APIs, e.g. Kimi-k2, GLM4.5 (from zai), Deepseek-v3.1.
361
+ Add these functions to your shell config (.bashrc/.zshrc):
256
362
 
257
363
  ```bash
258
364
  kimi() {
@@ -270,18 +376,30 @@ zai() {
270
376
  claude "$@"
271
377
  )
272
378
  }
379
+
380
+ dseek() {
381
+ (
382
+ export ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic
383
+ export ANTHROPIC_AUTH_TOKEN=${DEEPSEEK_API_KEY}
384
+ export ANTHROPIC_MODEL=deepseek-chat
385
+ export ANTHROPIC_SMALL_FAST_MODEL=deepseek-chat
386
+ claude "$@"
387
+ )
388
+ }
273
389
  ```
274
390
 
275
391
  After adding these functions:
276
- - Set your API keys: `export KIMI_API_KEY=your-kimi-key` and
277
- `export Z_API_KEY=your-z-key`
392
+ - Set your API keys: `export KIMI_API_KEY=your-kimi-key`,
393
+ `export Z_API_KEY=your-z-key`, `export DEEPSEEK_API_KEY=your-deepseek-key`
278
394
  - Run `kimi` to use Claude Code with the Kimi K2 LLM
279
395
  - Run `zai` to use Claude Code with the GLM-4.5 model
396
+ - Run `dseek` to use Claude Code with the DeepSeek model
280
397
 
281
398
  The functions use subshells to ensure the environment variables don't affect
282
399
  your main shell session, so you could be running multiple instances of Claude Code,
283
400
  each using a different LLM.
284
401
 
402
+ <a id="documentation"></a>
285
403
  ## 📚 Documentation
286
404
 
287
405
  - [tmux-cli detailed instructions](docs/tmux-cli-instructions.md) -
@@ -292,6 +410,7 @@ each using a different LLM.
292
410
  Complete guide for the .env backup system
293
411
  - [Hook configuration](hooks/README.md) - Setting up Claude Code hooks
294
412
 
413
+ <a id="requirements"></a>
295
414
  ## 📋 Requirements
296
415
 
297
416
  - Python 3.11+
@@ -299,6 +418,7 @@ each using a different LLM.
299
418
  - tmux (for tmux-cli functionality)
300
419
  - SOPS (for vault functionality)
301
420
 
421
+ <a id="development"></a>
302
422
  ## 🛠️ Development
303
423
 
304
424
  ### Setup
@@ -355,6 +475,7 @@ Run `make help` to see all available commands:
355
475
  - `make release` - Bump patch version and install globally
356
476
  - `make patch/minor/major` - Version bump commands
357
477
 
478
+ <a id="license"></a>
358
479
  ## 📄 License
359
480
 
360
481
  MIT
@@ -1,3 +1,3 @@
1
1
  """Claude Code Tools - Collection of utilities for Claude Code."""
2
2
 
3
- __version__ = "0.1.20"
3
+ __version__ = "0.2.0"
@@ -168,35 +168,56 @@ def search_keywords_in_file(filepath: Path, keywords: List[str]) -> tuple[bool,
168
168
  return matches, line_count, git_branch
169
169
 
170
170
 
171
+ def is_system_message(text: str) -> bool:
172
+ """Check if text is system-generated (XML tags, env context, etc)"""
173
+ if not text or len(text.strip()) < 5:
174
+ return True
175
+ text = text.strip()
176
+ # Check for XML-like tags (user_instructions, environment_context, etc)
177
+ if text.startswith("<") and ">" in text[:100]:
178
+ return True
179
+ return False
180
+
181
+
171
182
  def get_session_preview(filepath: Path) -> str:
172
- """Get a preview of the session from the first user message."""
183
+ """Get a preview of the session from the LAST user message."""
184
+ last_user_message = None
185
+
173
186
  try:
174
187
  with open(filepath, 'r', encoding='utf-8') as f:
175
188
  for line in f:
176
189
  try:
177
190
  data = json.loads(line.strip())
178
- if data.get('type') == 'message' and data.get('role') == 'user':
179
- content = data.get('content', '')
191
+ # Check top-level type for user messages
192
+ if data.get('type') == 'user':
193
+ message = data.get('message', {})
194
+ content = message.get('content', '')
195
+ text = None
196
+
180
197
  if isinstance(content, str):
181
- # Get first 60 chars of the message
182
- preview = content.strip().replace('\n', ' ')[:60]
183
- if len(content) > 60:
184
- preview += "..."
185
- return preview
198
+ text = content.strip()
186
199
  elif isinstance(content, list):
187
200
  # Handle structured content
188
201
  for item in content:
189
202
  if isinstance(item, dict) and item.get('type') == 'text':
190
- text = item.get('text', '')
191
- preview = text.strip().replace('\n', ' ')[:60]
192
- if len(text) > 60:
193
- preview += "..."
194
- return preview
203
+ text = item.get('text', '').strip()
204
+ break
205
+
206
+ # Filter out system messages and keep updating to get LAST message
207
+ if text and not is_system_message(text):
208
+ cleaned = text.replace('\n', ' ')[:400]
209
+ # Prefer substantial messages (>20 chars)
210
+ if len(cleaned) > 20:
211
+ last_user_message = cleaned
212
+ elif last_user_message is None:
213
+ last_user_message = cleaned
214
+
195
215
  except (json.JSONDecodeError, KeyError):
196
216
  continue
197
217
  except Exception:
198
218
  pass
199
- return "No preview available"
219
+
220
+ return last_user_message if last_user_message else "No preview available"
200
221
 
201
222
 
202
223
  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]]]:
@@ -307,7 +328,7 @@ def display_interactive_ui(sessions: List[Tuple[str, float, int, str, str, str,
307
328
  table.add_column("Branch", style="magenta")
308
329
  table.add_column("Date", style="blue")
309
330
  table.add_column("Lines", style="cyan", justify="right")
310
- table.add_column("Preview", style="white", overflow="fold")
331
+ table.add_column("Preview", style="white", max_width=60, overflow="fold")
311
332
 
312
333
  for idx, (session_id, mod_time, line_count, project_name, preview, _, git_branch) in enumerate(display_sessions, 1):
313
334
  mod_date = datetime.fromtimestamp(mod_time).strftime('%Y-%m-%d %H:%M')
@@ -0,0 +1,458 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Find and resume Codex sessions by searching keywords in session history.
4
+
5
+ Usage:
6
+ find-codex-session "keywords" [OPTIONS]
7
+ fcs-codex "keywords" [OPTIONS] # via shell wrapper
8
+
9
+ Examples:
10
+ find-codex-session "langroid,MCP" # Current project only
11
+ find-codex-session "error,debugging" -g # All projects
12
+ find-codex-session "keywords" -n 5 # Limit results
13
+ fcs-codex "keywords" --shell # Via shell wrapper
14
+ """
15
+
16
+ import argparse
17
+ import json
18
+ import os
19
+ import re
20
+ import shlex
21
+ import subprocess
22
+ import sys
23
+ from datetime import datetime
24
+ from pathlib import Path
25
+ from typing import Optional
26
+
27
+ try:
28
+ from rich.console import Console
29
+ from rich.table import Table
30
+
31
+ RICH_AVAILABLE = True
32
+ except ImportError:
33
+ RICH_AVAILABLE = False
34
+
35
+
36
+ def get_codex_home(custom_home: Optional[str] = None) -> Path:
37
+ """Get the Codex home directory."""
38
+ if custom_home:
39
+ return Path(custom_home).expanduser()
40
+ return Path.home() / ".codex"
41
+
42
+
43
+ def extract_session_id_from_filename(filename: str) -> Optional[str]:
44
+ """
45
+ Extract session ID from Codex session filename.
46
+
47
+ Format: rollout-YYYY-MM-DDTHH-MM-SS-<SESSION_ID>.jsonl
48
+ Returns: SESSION_ID portion
49
+ """
50
+ # Pattern: anything after the timestamp part
51
+ # e.g., rollout-2025-10-07T13-48-15-0199bfc9-c444-77e1-8c8a-f91c94fcd832.jsonl
52
+ match = re.match(
53
+ r"rollout-\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-(.+)\.jsonl", filename
54
+ )
55
+ if match:
56
+ return match.group(1)
57
+ return None
58
+
59
+
60
+ def extract_session_metadata(session_file: Path) -> Optional[dict]:
61
+ """
62
+ Extract metadata from the first session_meta entry in a Codex session file.
63
+
64
+ Returns dict with: id, cwd, branch, timestamp
65
+ """
66
+ try:
67
+ with open(session_file, "r", encoding="utf-8") as f:
68
+ for line in f:
69
+ if not line.strip():
70
+ continue
71
+ try:
72
+ entry = json.loads(line)
73
+ if entry.get("type") == "session_meta":
74
+ payload = entry.get("payload", {})
75
+ git_info = payload.get("git", {})
76
+ return {
77
+ "id": payload.get("id", ""),
78
+ "cwd": payload.get("cwd", ""),
79
+ "branch": git_info.get("branch", ""),
80
+ "timestamp": payload.get("timestamp", ""),
81
+ }
82
+ except json.JSONDecodeError:
83
+ continue
84
+ return None
85
+ except (OSError, IOError):
86
+ return None
87
+
88
+
89
+ def get_project_name(cwd: str) -> str:
90
+ """Extract project name from working directory path."""
91
+ if not cwd:
92
+ return "unknown"
93
+ path = Path(cwd)
94
+ return path.name if path.name else "unknown"
95
+
96
+
97
+ def is_system_message(text: str) -> bool:
98
+ """Check if text is system-generated (XML tags, env context, etc)"""
99
+ if not text or len(text.strip()) < 5:
100
+ return True
101
+ text = text.strip()
102
+ # Check for XML-like tags (user_instructions, environment_context, etc)
103
+ if text.startswith("<") and ">" in text[:100]:
104
+ return True
105
+ return False
106
+
107
+
108
+ def search_keywords_in_file(
109
+ session_file: Path, keywords: list[str]
110
+ ) -> tuple[bool, int, Optional[str]]:
111
+ """
112
+ Search for keywords in a Codex session file.
113
+
114
+ Returns: (found, line_count, preview)
115
+ - found: True if all keywords found (case-insensitive AND logic)
116
+ - line_count: total lines in file
117
+ - preview: best user message content (skips system messages)
118
+ """
119
+ keywords_lower = [k.lower() for k in keywords]
120
+ found_keywords = set()
121
+ line_count = 0
122
+ last_user_message = None # Keep track of the LAST user message
123
+
124
+ try:
125
+ with open(session_file, "r", encoding="utf-8") as f:
126
+ for line in f:
127
+ line_count += 1
128
+ if not line.strip():
129
+ continue
130
+
131
+ try:
132
+ entry = json.loads(line)
133
+
134
+ # Extract user messages (skip system messages)
135
+ # Keep updating to get the LAST one
136
+ if (
137
+ entry.get("type") == "response_item"
138
+ and entry.get("payload", {}).get("role") == "user"
139
+ ):
140
+ content = entry.get("payload", {}).get("content", [])
141
+ if isinstance(content, list) and len(content) > 0:
142
+ first_item = content[0]
143
+ if isinstance(first_item, dict):
144
+ text = first_item.get("text", "")
145
+ if text and not is_system_message(text):
146
+ # Keep updating with latest message
147
+ cleaned = text[:400].replace("\n", " ").strip()
148
+ # Only keep if it's substantial (>20 chars)
149
+ if len(cleaned) > 20:
150
+ last_user_message = cleaned
151
+ elif last_user_message is None:
152
+ # Keep even short messages if no better option
153
+ last_user_message = cleaned
154
+
155
+ # Search for keywords in all text content
156
+ line_lower = line.lower()
157
+ for kw in keywords_lower:
158
+ if kw in line_lower:
159
+ found_keywords.add(kw)
160
+
161
+ except json.JSONDecodeError:
162
+ continue
163
+
164
+ all_found = len(found_keywords) == len(keywords_lower)
165
+ return all_found, line_count, last_user_message
166
+
167
+ except (OSError, IOError):
168
+ return False, 0, None
169
+
170
+
171
+ def find_sessions(
172
+ codex_home: Path,
173
+ keywords: list[str],
174
+ num_matches: int = 10,
175
+ global_search: bool = False,
176
+ ) -> list[dict]:
177
+ """
178
+ Find Codex sessions matching keywords.
179
+
180
+ Args:
181
+ codex_home: Path to Codex home directory
182
+ keywords: List of keywords to search for
183
+ num_matches: Maximum number of results to return
184
+ global_search: If False, filter to current directory only
185
+
186
+ Returns list of dicts with: session_id, project, branch, date,
187
+ lines, preview, cwd, file_path
188
+ """
189
+ sessions_dir = codex_home / "sessions"
190
+ if not sessions_dir.exists():
191
+ return []
192
+
193
+ # Get current directory for filtering (if not global search)
194
+ current_cwd = os.getcwd() if not global_search else None
195
+
196
+ matches = []
197
+
198
+ # Walk through YYYY/MM/DD directory structure
199
+ for year_dir in sorted(sessions_dir.iterdir(), reverse=True):
200
+ if not year_dir.is_dir():
201
+ continue
202
+
203
+ for month_dir in sorted(year_dir.iterdir(), reverse=True):
204
+ if not month_dir.is_dir():
205
+ continue
206
+
207
+ for day_dir in sorted(month_dir.iterdir(), reverse=True):
208
+ if not day_dir.is_dir():
209
+ continue
210
+
211
+ # Process all JSONL files in this day
212
+ session_files = sorted(
213
+ day_dir.glob("rollout-*.jsonl"), reverse=True
214
+ )
215
+
216
+ for session_file in session_files:
217
+ # Search for keywords
218
+ found, line_count, preview = search_keywords_in_file(
219
+ session_file, keywords
220
+ )
221
+
222
+ if not found:
223
+ continue
224
+
225
+ # Extract metadata
226
+ metadata = extract_session_metadata(session_file)
227
+ if not metadata:
228
+ # Fallback: extract session ID from filename
229
+ session_id = extract_session_id_from_filename(
230
+ session_file.name
231
+ )
232
+ if not session_id:
233
+ continue
234
+ metadata = {
235
+ "id": session_id,
236
+ "cwd": "",
237
+ "branch": "",
238
+ "timestamp": "",
239
+ }
240
+
241
+ # Filter by current directory if not global search
242
+ if current_cwd and metadata["cwd"] != current_cwd:
243
+ continue
244
+
245
+ # Parse timestamp
246
+ timestamp_str = metadata["timestamp"]
247
+ if timestamp_str:
248
+ try:
249
+ dt = datetime.fromisoformat(
250
+ timestamp_str.replace("Z", "+00:00")
251
+ )
252
+ date_str = dt.strftime("%Y-%m-%d %H:%M")
253
+ except ValueError:
254
+ date_str = timestamp_str[:16]
255
+ else:
256
+ # Fallback to directory date
257
+ date_str = f"{year_dir.name}-{month_dir.name}-{day_dir.name}"
258
+
259
+ matches.append(
260
+ {
261
+ "session_id": metadata["id"],
262
+ "project": get_project_name(metadata["cwd"]),
263
+ "branch": metadata["branch"] or "",
264
+ "date": date_str,
265
+ "lines": line_count,
266
+ "preview": preview or "No preview",
267
+ "cwd": metadata["cwd"],
268
+ "file_path": str(session_file),
269
+ }
270
+ )
271
+
272
+ # Early exit if we have enough matches
273
+ if len(matches) >= num_matches * 3:
274
+ break
275
+
276
+ # Sort by date (reverse chronological) and limit
277
+ matches.sort(key=lambda x: x["date"], reverse=True)
278
+ return matches[:num_matches]
279
+
280
+
281
+ def display_interactive_ui(
282
+ matches: list[dict],
283
+ ) -> Optional[tuple[str, str]]:
284
+ """
285
+ Display matches in interactive UI and get user selection.
286
+
287
+ Returns: (session_id, cwd) or None if cancelled
288
+ """
289
+ if not matches:
290
+ print("No matching sessions found.")
291
+ return None
292
+
293
+ if RICH_AVAILABLE:
294
+ console = Console()
295
+ table = Table(title="Codex Sessions", show_header=True)
296
+ table.add_column("#", style="cyan", justify="right")
297
+ table.add_column("Session ID", style="yellow", no_wrap=True)
298
+ table.add_column("Project", style="green")
299
+ table.add_column("Branch", style="magenta")
300
+ table.add_column("Date", style="blue")
301
+ table.add_column("Lines", justify="right")
302
+ table.add_column("Preview", style="dim", max_width=60, overflow="fold")
303
+
304
+ for i, match in enumerate(matches, 1):
305
+ table.add_row(
306
+ str(i),
307
+ match["session_id"][:16] + "...",
308
+ match["project"],
309
+ match["branch"],
310
+ match["date"],
311
+ str(match["lines"]),
312
+ match["preview"], # No truncation, let Rich wrap it
313
+ )
314
+
315
+ console.print(table)
316
+ else:
317
+ # Fallback to plain text
318
+ print("\nMatching Codex Sessions:")
319
+ print("-" * 80)
320
+ for i, match in enumerate(matches, 1):
321
+ print(f"{i}. {match['session_id'][:16]}...")
322
+ print(f" Project: {match['project']}")
323
+ print(f" Branch: {match['branch']}")
324
+ print(f" Date: {match['date']}")
325
+ print(f" Preview: {match['preview'][:60]}...")
326
+ print()
327
+
328
+ # Get user selection
329
+ if len(matches) == 1:
330
+ print(f"\nAuto-selecting only match: {matches[0]['session_id'][:16]}...")
331
+ return matches[0]["session_id"], matches[0]["cwd"]
332
+
333
+ try:
334
+ choice = input(
335
+ "\nEnter number to resume session (or Enter to cancel): "
336
+ ).strip()
337
+ if not choice:
338
+ return None
339
+
340
+ idx = int(choice) - 1
341
+ if 0 <= idx < len(matches):
342
+ return matches[idx]["session_id"], matches[idx]["cwd"]
343
+ else:
344
+ print("Invalid selection.")
345
+ return None
346
+ except (ValueError, KeyboardInterrupt):
347
+ print("\nCancelled.")
348
+ return None
349
+
350
+
351
+ def resume_session(
352
+ session_id: str, cwd: str, shell_mode: bool = False
353
+ ) -> None:
354
+ """
355
+ Resume a Codex session.
356
+
357
+ In shell mode: outputs commands for eval
358
+ In interactive mode: executes codex resume
359
+ """
360
+ if shell_mode:
361
+ # Output commands for shell eval
362
+ # Redirect prompts to stderr, commands to stdout
363
+ if cwd and cwd != os.getcwd():
364
+ print(f"cd {shlex.quote(cwd)}", file=sys.stdout)
365
+ print(f"codex resume {shlex.quote(session_id)}", file=sys.stdout)
366
+ else:
367
+ # Interactive mode
368
+ if cwd and cwd != os.getcwd():
369
+ response = input(
370
+ f"\nSession is in different directory: {cwd}\n"
371
+ "Change directory and resume? [Y/n]: "
372
+ ).strip()
373
+ if response.lower() in ("", "y", "yes"):
374
+ try:
375
+ os.chdir(cwd)
376
+ print(f"Changed to: {cwd}")
377
+ except OSError as e:
378
+ print(f"Error changing directory: {e}")
379
+ return
380
+
381
+ # Execute codex resume
382
+ try:
383
+ os.execvp("codex", ["codex", "resume", session_id])
384
+ except OSError as e:
385
+ print(f"Error launching codex: {e}")
386
+ sys.exit(1)
387
+
388
+
389
+ def main():
390
+ """Main entry point."""
391
+ parser = argparse.ArgumentParser(
392
+ description="Find and resume Codex sessions by keyword search",
393
+ formatter_class=argparse.RawDescriptionHelpFormatter,
394
+ epilog="""
395
+ Examples:
396
+ find-codex-session "langroid,MCP" # Current project only
397
+ find-codex-session "error,debugging" -g # All projects
398
+ find-codex-session "keywords" -n 5 # Limit results
399
+ fcs-codex "keywords" --shell # Via shell wrapper
400
+ """,
401
+ )
402
+
403
+ parser.add_argument(
404
+ "keywords",
405
+ help="Comma-separated keywords to search (AND logic)",
406
+ )
407
+ parser.add_argument(
408
+ "-g",
409
+ "--global",
410
+ dest="global_search",
411
+ action="store_true",
412
+ help="Search all projects (default: current project only)",
413
+ )
414
+ parser.add_argument(
415
+ "-n",
416
+ "--num-matches",
417
+ type=int,
418
+ default=10,
419
+ help="Number of matches to display (default: 10)",
420
+ )
421
+ parser.add_argument(
422
+ "--shell",
423
+ action="store_true",
424
+ help="Output shell commands for eval (enables persistent cd)",
425
+ )
426
+ parser.add_argument(
427
+ "--codex-home",
428
+ help="Custom Codex home directory (default: ~/.codex)",
429
+ )
430
+
431
+ args = parser.parse_args()
432
+
433
+ # Parse keywords
434
+ keywords = [k.strip() for k in args.keywords.split(",") if k.strip()]
435
+ if not keywords:
436
+ print("Error: No keywords provided", file=sys.stderr)
437
+ sys.exit(1)
438
+
439
+ # Get Codex home
440
+ codex_home = get_codex_home(args.codex_home)
441
+ if not codex_home.exists():
442
+ print(f"Error: Codex home not found: {codex_home}", file=sys.stderr)
443
+ sys.exit(1)
444
+
445
+ # Find matching sessions
446
+ matches = find_sessions(
447
+ codex_home, keywords, args.num_matches, args.global_search
448
+ )
449
+
450
+ # Display and get selection
451
+ result = display_interactive_ui(matches)
452
+ if result:
453
+ session_id, cwd = result
454
+ resume_session(session_id, cwd, args.shell)
455
+
456
+
457
+ if __name__ == "__main__":
458
+ main()
@@ -0,0 +1,48 @@
1
+ # lmsh
2
+
3
+ A fast, minimal natural language shell interface that translates conversational commands into editable shell commands.
4
+
5
+ ## What it does
6
+
7
+ Type natural language → Get an editable shell command → Review/modify → Execute
8
+
9
+ Example:
10
+ ```bash
11
+ $ lmsh
12
+ > show me all python files modified today
13
+ find . -name "*.py" -mtime 0 # <-- Editable command appears, press Enter to run
14
+ ```
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ # Install from crates.io (easiest, requires Rust)
20
+ cargo install lmsh
21
+
22
+ # Or build from source
23
+ cd lmsh/
24
+ cargo build --release
25
+ cp target/release/lmsh ~/.cargo/bin/
26
+ # Or: make lmsh-install
27
+ ```
28
+
29
+ Note: Ensure `~/.cargo/bin` is in your PATH.
30
+
31
+ ## Usage
32
+
33
+ ```bash
34
+ lmsh # Interactive mode
35
+ lmsh "show me python files" # Translate, edit, execute, then interactive mode
36
+ lmsh --version # Version info
37
+ ```
38
+
39
+ ## Features
40
+
41
+ - **Editable commands** - Review and modify before execution
42
+ - **Fast startup** - Optimized Rust binary (~1ms)
43
+ - **Claude-powered** - Leverages your existing Claude Code CLI by calling `claude -p <prompt>` in non-interactive mode
44
+ - **Shell preservation** - Maintains your shell environment and aliases
45
+
46
+ ## Note
47
+
48
+ This tool requires the Claude Code CLI (`claude` command) to be installed and configured. The translation step adds ~2-3s latency due to Claude Code CLI startup time.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "claude-code-tools"
3
- version = "0.1.20"
3
+ version = "0.2.0"
4
4
  description = "Collection of tools for working with Claude Code"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -16,6 +16,7 @@ dev = ["commitizen>=3.0.0"]
16
16
 
17
17
  [project.scripts]
18
18
  find-claude-session = "claude_code_tools.find_claude_session:main"
19
+ find-codex-session = "claude_code_tools.find_codex_session:main"
19
20
  vault = "claude_code_tools.dotenv_vault:main"
20
21
  tmux-cli = "claude_code_tools.tmux_cli_controller:main"
21
22
  env-safe = "claude_code_tools.env_safe:main"
@@ -42,7 +43,7 @@ exclude = [
42
43
 
43
44
  [tool.commitizen]
44
45
  name = "cz_conventional_commits"
45
- version = "0.1.20"
46
+ version = "0.2.0"
46
47
  tag_format = "v$version"
47
48
  version_files = [
48
49
  "pyproject.toml:version",