| name | tools-cli-guide |
| description | Standalone CLI wrapper exposing Claude Code's core file operations (read, glob, grep, edit, write) as command-line utilities. Use for scripting and automation without requiring a full Claude session. |
Tools CLI — Standalone File Operations Wrapper
Status: ✅ Production Ready | Version: 1.0.0 | Binary: 20.1 MB
Source: /home/project/cc-src/src/entrypoints/tools-cli.ts
Compiled: /home/project/cc-src/dist/tools-cli.js
Overview
Tools CLI is a standalone wrapper that exposes Claude Code's core file operations as a command-line utility. It provides 5 tools for file manipulation without requiring a full Claude session.
Key Feature: Runs in bypass mode — no permission prompts, suitable for scripting and automation.
Quick Start
export USE_BUILTIN_RIPGREP=false
alias tools-cli='bun /home/project/cc-src/dist/tools-cli.js'
tools-cli --version
tools-cli read --file README.md
tools-cli glob --pattern "*.ts"
tools-cli grep --pattern "function"
Commands Reference
1. read — Read File Contents
Read text files with optional pagination and output formatting.
tools-cli read --file /path/to/file.md
tools-cli read --file README.md --limit 10
tools-cli read --file README.md --offset 10 --limit 5
tools-cli read --file README.md --limit 5 --json
tools-cli read --file README.md --silent
Options:
| Option | Type | Description |
|---|
--file <path> | string | File path (supports ~ expansion) |
--offset <n> | number | Start reading from line n |
--limit <n> | number | Maximum lines to read |
--json | flag | Output as structured JSON |
--silent | flag | Raw content only (no metadata) |
JSON Output Example:
{
"type": "text",
"file": {
"filePath": "/home/project/cc-src/README.md",
"content": "# CC-SRC: Claude Code...",
"numLines": 5,
"startLine": 1,
"totalLines": 377
}
}
2. write — Create or Overwrite Files
Write content to files. Creates new files or overwrites existing ones.
tools-cli write --file /tmp/test.txt --content "Hello World"
tools-cli write --file config.json --content '{"debug": true}'
tools-cli write --file /tmp/notes.txt --content "Line 1
Line 2
Line 3"
echo "Dynamic content" | tools-cli write --file /tmp/output.txt --content "$(cat)"
Options:
| Option | Type | Description |
|---|
--file <path> | string | Target file path |
--content <text> | string | Content to write |
Note: write bypasses the file state cache — safe for CLI one-liners.
3. edit — String Replacement
Edit files by replacing exact string matches. ⚠️ Has cache protection (see below).
tools-cli edit --file config.json \
--old '"debug": false' \
--new '"debug": true"'
tools-cli edit --file README.md \
--old "Old Title" \
--new "New Title"
Options:
| Option | Type | Description |
|---|
--file <path> | string | File to edit |
--old <text> | string | Exact text to replace |
--new <text> | string | Replacement text |
⚠️ Cache Protection (By Design):
The edit tool enforces a Read-Modify-Write pattern for safety:
- Requires the file to have been read in the same session
- Rejects edits if file was modified since last read
- Prevents race conditions and lost changes
Why this affects CLI: Each CLI invocation is a fresh process with an empty cache. The edit tool sees no prior read and fails with:
Error: File has not been read yet. Read it first before writing to it.
For CLI usage: Each tools-cli invocation is a separate process, so read → edit won't work across invocations. For CLI scripts, use the read → write pattern with content replacement:
content=$(tools-cli read --file config.json --silent)
new_content="${content/false/true}"
tools-cli write --file config.json --content "$new_content"
Note: In agent sessions (OpenClaw, Claude Code), read → edit works because the cache persists across tool calls.
4. glob — File Pattern Matching
Find files using glob patterns. Requires USE_BUILTIN_RIPGREP=false.
tools-cli glob --pattern "*.ts" --path src/
tools-cli glob --pattern "src/**/*.tsx"
tools-cli glob --pattern "*.{ts,tsx,js}"
tools-cli glob --pattern "*.md" --head 10
Options:
| Option | Type | Description |
|---|
--pattern <glob> | string | Glob pattern (supports **, *, ?, []) |
--path <dir> | string | Search directory (default: cwd) |
--head <n> | number | Limit results |
--silent | flag | File paths only (no formatting) |
5. grep — Content Search
Search file contents with regex. Requires USE_BUILTIN_RIPGREP=false.
tools-cli grep --pattern "TODO|FIXME" --path src/
tools-cli grep --pattern "error" --path logs/ --mode content -i
tools-cli grep --pattern "function" --path src/ --context 2
tools-cli grep --pattern "class.*Tool" --path src/tools --head 5
Options:
| Option | Type | Description |
|---|
--pattern <regex> | string | Regex pattern to search |
--path <dir> | string | Search directory |
--mode <type> | string | content or files (default: files) |
--head <n> | number | Limit results |
-i | flag | Case-insensitive |
--context <n> | number | Show n lines of context |
Global Options
All commands support these options:
| Option | Description |
|---|
--cwd <path> | Set working directory |
--json | Output as JSON |
--silent | Suppress non-error output |
--verbose | Show debug information |
--help | Show help message |
--version | Show version |
Environment Requirements
USE_BUILTIN_RIPGREP=false (REQUIRED for glob/grep)
The glob and grep commands require system rg (ripgrep) to be available.
export USE_BUILTIN_RIPGREP=false
echo 'export USE_BUILTIN_RIPGREP=false' >> ~/.bashrc
Why needed: The bundled ripgrep path (src/utils/vendor/ripgrep/) may not resolve correctly. Setting this flag uses the system-installed rg instead.
File State Cache Explained
What It Is
The FileStateCache (src/utils/fileStateCache.ts) is a safety mechanism, not a bug. It exists to prevent a critical race condition: editing a file that has been modified by another process since you read it.
What it tracks:
- File content at time of read
- Timestamp of read
- Read parameters (offset/limit for partial reads)
- Whether the read was a partial view
Why it matters: Without this protection, you'd silently overwrite changes from linters, formatters, other agents, or users working concurrently.
How It Works
┌─────────────────────────────────────────────────────────────────┐
│ FILE STATE CACHE FLOW │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. READ TOOL 2. EDIT TOOL │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Read file │ │ Validate input │ │
│ │ ↓ │ │ ↓ │ │
│ │ Store in cache: │ │ Check cache: │ │
│ │ - content │ │ - File read? │ │
│ │ - timestamp │ │ - Stale? │ │
│ │ - offset/limit │ │ - Partial view?│ │
│ └─────────────────┘ │ ↓ │ │
│ │ Allow or reject │ │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
The validation logic checks:
- Has the file been read in this session?
- Was it a full read or partial view?
- Has the file been modified externally since read?
⚠️ CRITICAL INSIGHT: The Cache Is a Safety Signal, Not an Obstacle
When edit fails with "File has not been read yet":
| ❌ WRONG (Bypass) | ✅ RIGHT (Respect) |
|---|
edit fails → switch to write | edit fails → read → retry edit |
| Bypass the safety mechanism | Trust and follow the safety mechanism |
| Risk overwriting concurrent changes | Preserve concurrent modifications |
| Treat failure as obstacle | Treat failure as useful signal |
Why not jump to write?
If another process modified the file after you read it but before your edit, write will silently overwrite those changes. The cache failure is telling you: "You don't know what's actually in this file right now."
The correct recovery pattern:
tools-cli read --file config.json --silent
tools-cli edit --file config.json --old "old_value" --new "new_value"
Why CLI Has Cache Issues
Root cause (src/entrypoints/tools-cli.ts:268):
readFileState: new Map(),
Each CLI invocation creates a fresh Map() with no memory of prior reads. The edit tool checks this empty cache and fails.
Context matters:
- In OpenClaw agent sessions:
read → edit works because the cache persists across tool calls
- In CLI invocations: Each call is a separate process, so
read → edit still fails
Affected Tools
| Tool | Affected? | Reason |
|---|
read | ❌ No | Cache population, not consumption |
write | ❌ No | Creates/overwrites without cache check |
edit | ✅ Yes | Requires cached read state |
glob | ❌ No | No file state dependency |
grep | ❌ No | No file state dependency |
When to Use Each Approach
Use read → edit when:
- Working in an agent session (OpenClaw, Claude Code)
- The file might have been modified by concurrent processes
- You want to preserve others' changes
Use write for CLI scripts when:
Advanced: Content Comparison Fallback
On systems where timestamps can change without content changes (Windows cloud sync, antivirus), the cache includes a content comparison fallback. For full reads, if the timestamp indicates modification but content matches, the edit is still allowed. This prevents false positives while maintaining safety.
Performance Characteristics
| Operation | Typical Time | Notes |
|---|
| Help display | ~500ms | CLI startup overhead |
| File read | ~100ms | Small files (<1MB) |
| Glob search | ~200ms | Depends on file count |
| Grep search | ~300ms | Depends on content size |
| Write operation | ~50ms | Small files |
Binary size: 20.1 MB (5,180 modules bundled)
Security Model
✅ No permission prompts — Runs in bypass mode
✅ Explicit invocation = consent — User knows what they're running
✅ Same protections as main CLI — Path traversal, blocked devices, etc.
⚠️ User responsible — Same caution as rm, cp, echo > file
Recommendation: Review commands before running in production. No undo mechanism.
Use Cases
1. Script Integration
#!/bin/bash
for file in $(tools-cli glob --pattern "*.md" --silent); do
echo "Processing: $file"
content=$(tools-cli read --file "$file" --silent)
done
2. CI/CD Pipeline
- name: Update version
run: |
content=$(tools-cli read --file package.json --silent)
new_content="${content/\"version\": \"1.0.0\"/\"version\": \"1.0.1\"}"
tools-cli write --file package.json --content "$new_content"
3. Quick File Operations
tools-cli read --file $(tools-cli glob --pattern "README.md" --silent | head -1)
tools-cli grep --pattern "TODO|FIXME" --path src/ --mode content --head 20
echo "Generated content" | tools-cli write --file /tmp/output.txt --content "$(cat)"
Troubleshooting
"File has not been read yet" or "File has been modified since read" (Edit tool)
Cause: The FileStateCache requires a prior read to validate the edit is safe.
⚠️ DON'T: Jump to write — Jumping to write bypasses the safety mechanism and risks overwriting concurrent changes.
✅ DO: Read first, then retry edit
tools-cli read --file /path/to/file
tools-cli edit --file /path/to/file --old "old_text" --new "new_text"
For CLI scripts (separate invocations), use read → write with content replacement.
"ripgrep not found" (Glob/Grep tools)
Cause: USE_BUILTIN_RIPGREP not set or system rg not installed.
Solution:
export USE_BUILTIN_RIPGREP=false
which rg || sudo apt install ripgrep
Slow startup
Cause: 20MB binary has initialization overhead (~500ms).
Solution: Acceptable for script usage. For interactive use, consider shell aliases.
Related Documentation
- Test Report:
/home/project/cc-src/TOOLS_CLI_TEST_REPORT.md
- Cache Analysis:
/home/pete/.openclaw/workspace/documents/tools-cli_cache_issue_analysis.md
- Implementation Guide:
/home/project/cc-src/TOOLS_CLI_IMPLEMENTATION_GUIDE.md
- Security Model:
/home/project/cc-src/TOOLS_CLI_SECURITY_MODEL.md
Quick Reference Card
export USE_BUILTIN_RIPGREP=false
alias tools-cli='bun /home/project/cc-src/dist/tools-cli.js'
tools-cli read --file README.md --limit 10
tools-cli read --file README.md --offset 5 --limit 3 --json
tools-cli read --file README.md --silent
tools-cli write --file /tmp/test.txt --content "Hello World"
tools-cli edit --file config.json --old "false" --new "true"
tools-cli glob --pattern "*.ts" --path src/
tools-cli glob --pattern "src/**/*.tsx" --head 10
tools-cli grep --pattern "TODO" --path src/ --head 5
tools-cli grep --pattern "class.*Tool" --path src/tools --head 3
Last updated: 2026-04-14 07:04 SGT | Updated with cache safety insights | Verified against tools-cli v1.0.0