The auto-backgrounding timeout was changed to 30 seconds but documentation still referenced 45 seconds. Updated all occurrences.
278 lines
5.8 KiB
Markdown
278 lines
5.8 KiB
Markdown
# hanzo-tools-shell
|
|
|
|
Unified command execution with DAG support, auto-backgrounding, and process management. The shell toolkit provides the `cmd`, `zsh`, `bash`, and `ps` tools.
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
pip install hanzo-tools-shell
|
|
```
|
|
|
|
Or as part of the full toolkit:
|
|
|
|
```bash
|
|
pip install hanzo-mcp[tools-all]
|
|
```
|
|
|
|
## Overview
|
|
|
|
`hanzo-tools-shell` provides:
|
|
|
|
- **cmd** - Unified command execution with DAG support
|
|
- **zsh** / **bash** / **shell** - Shell-specific execution
|
|
- **ps** - Process management (list, kill, logs)
|
|
- **npx** / **uvx** - Package runner tools
|
|
- **open** - Platform-aware file/URL opener
|
|
|
|
## Key Features
|
|
|
|
### Auto-Backgrounding
|
|
|
|
Commands automatically background after 30 seconds (configurable):
|
|
|
|
```python
|
|
# Long-running command auto-backgrounds
|
|
cmd("npm run build") # If takes >30s, continues in background
|
|
|
|
# Check status later
|
|
ps() # List all background processes
|
|
ps(logs="cmd_abc123") # View output
|
|
ps(kill="cmd_abc123") # Stop process
|
|
```
|
|
|
|
### DAG Execution
|
|
|
|
Execute commands with dependency graphs:
|
|
|
|
```python
|
|
# Sequential (default)
|
|
cmd(["mkdir dist", "cp files dist/", "zip -r out.zip dist/"])
|
|
|
|
# Parallel
|
|
cmd(["npm install", "cargo build"], parallel=True)
|
|
|
|
# Mixed DAG with dependencies
|
|
cmd([
|
|
"mkdir -p dist",
|
|
{"parallel": ["cp a.txt dist/", "cp b.txt dist/"]},
|
|
"zip -r out.zip dist/"
|
|
])
|
|
```
|
|
|
|
## Tools Reference
|
|
|
|
### cmd
|
|
|
|
The primary command execution tool:
|
|
|
|
```python
|
|
# Simple command
|
|
cmd("ls -la")
|
|
|
|
# Sequential commands
|
|
cmd(["git status", "git diff", "git log -5"])
|
|
|
|
# Parallel execution
|
|
cmd(["npm install", "pip install -r requirements.txt"], parallel=True)
|
|
|
|
# Mixed DAG
|
|
cmd([
|
|
"mkdir -p dist",
|
|
{"parallel": ["build-frontend", "build-backend"]},
|
|
"deploy"
|
|
])
|
|
|
|
# With options
|
|
cmd(
|
|
"npm test",
|
|
timeout=120, # Command timeout in seconds
|
|
cwd="/project", # Working directory
|
|
env={"NODE_ENV": "test"}, # Environment variables
|
|
quiet=True, # Suppress stdout
|
|
strict=True # Stop on first error
|
|
)
|
|
```
|
|
|
|
### zsh / bash / shell
|
|
|
|
Shell-specific wrappers around `cmd`:
|
|
|
|
```python
|
|
# Use specific shell
|
|
zsh("echo $SHELL") # Uses zsh
|
|
bash("echo $SHELL") # Uses bash
|
|
shell("echo $SHELL") # Auto-detects: zsh > bash > sh
|
|
|
|
# Same options as cmd
|
|
zsh(["cmd1", "cmd2"], parallel=True, timeout=60)
|
|
```
|
|
|
|
### ps
|
|
|
|
Process management for background commands:
|
|
|
|
```python
|
|
# List all background processes
|
|
ps()
|
|
|
|
# Get specific process info
|
|
ps(id="cmd_abc123")
|
|
|
|
# View process output (last 100 lines by default)
|
|
ps(logs="cmd_abc123")
|
|
ps(logs="cmd_abc123", n=50) # Last 50 lines
|
|
|
|
# Kill process
|
|
ps(kill="cmd_abc123") # SIGTERM (default)
|
|
ps(kill="cmd_abc123", sig=9) # SIGKILL
|
|
```
|
|
|
|
### npx / uvx
|
|
|
|
Package runners with auto-backgrounding:
|
|
|
|
```python
|
|
# Run npm packages
|
|
npx("create-react-app my-app")
|
|
npx("http-server -p 8080") # Auto-backgrounds if long-running
|
|
|
|
# Run Python packages
|
|
uvx("ruff check .")
|
|
uvx("mkdocs serve") # Auto-backgrounds if long-running
|
|
```
|
|
|
|
### open
|
|
|
|
Platform-aware opener for files and URLs:
|
|
|
|
```python
|
|
# Open URLs
|
|
open("https://example.com")
|
|
|
|
# Open files with default application
|
|
open("./document.pdf")
|
|
open("/path/to/image.png")
|
|
```
|
|
|
|
## Configuration
|
|
|
|
### Environment Variables
|
|
|
|
| Variable | Default | Description |
|
|
|----------|---------|-------------|
|
|
| `HANZO_AUTO_BACKGROUND_TIMEOUT` | `45` | Seconds before auto-backgrounding (0 to disable) |
|
|
| `HANZO_DEFAULT_SHELL` | `zsh` | Default shell for execution |
|
|
|
|
### Timeout Configuration
|
|
|
|
```python
|
|
# Disable auto-backgrounding for a command
|
|
cmd("long-running-command", timeout=0)
|
|
|
|
# Set global timeout via environment
|
|
import os
|
|
os.environ["HANZO_AUTO_BACKGROUND_TIMEOUT"] = "120" # 2 minutes
|
|
```
|
|
|
|
## Examples
|
|
|
|
### Build Pipeline
|
|
|
|
```python
|
|
# Parallel build with sequential deploy
|
|
cmd([
|
|
{"parallel": [
|
|
"npm run build:frontend",
|
|
"npm run build:backend",
|
|
"npm run build:docs"
|
|
]},
|
|
"npm run test",
|
|
"npm run deploy"
|
|
])
|
|
```
|
|
|
|
### Development Server
|
|
|
|
```python
|
|
# Start dev server (auto-backgrounds)
|
|
cmd("npm run dev")
|
|
|
|
# Later, check status
|
|
ps()
|
|
# Output: cmd_abc123 | running | npm run dev | started 5m ago
|
|
|
|
# View logs
|
|
ps(logs="cmd_abc123")
|
|
|
|
# Stop when done
|
|
ps(kill="cmd_abc123")
|
|
```
|
|
|
|
### Tool Invocation in DAG
|
|
|
|
```python
|
|
# Mix shell commands with tool calls
|
|
cmd([
|
|
"git pull origin main",
|
|
{"tool": "search", "input": {"pattern": "TODO"}},
|
|
"npm test"
|
|
])
|
|
```
|
|
|
|
### Parallel File Operations
|
|
|
|
```python
|
|
# Process multiple files in parallel
|
|
cmd([
|
|
["process file1.txt", "process file2.txt", "process file3.txt"]
|
|
], parallel=True)
|
|
```
|
|
|
|
## Architecture
|
|
|
|
### ShellExecutor
|
|
|
|
Single async execution engine for all shell tools:
|
|
|
|
```
|
|
CmdTool (cmd) # Unified execution graph (DAG)
|
|
├── ZshTool (zsh) # Thin shim: shell=zsh
|
|
├── BashTool (bash) # Thin shim: shell=bash
|
|
└── ShellTool # Smart shim: zsh > bash > sh
|
|
|
|
ShellExecutor (singleton)
|
|
└── ProcessManager (singleton)
|
|
└── ps tool (process listing/kill/logs)
|
|
```
|
|
|
|
### Process Lifecycle
|
|
|
|
1. Command starts with `asyncio.create_subprocess_exec`
|
|
2. Wait for completion with configured timeout
|
|
3. If timeout reached: register with ProcessManager, continue in background
|
|
4. User can monitor with `ps --logs <id>` or stop with `ps --kill <id>`
|
|
|
|
## Error Handling
|
|
|
|
```python
|
|
# Strict mode: stop on first error
|
|
cmd(["cmd1", "cmd2", "cmd3"], strict=True)
|
|
|
|
# Default: continue on errors, report all
|
|
cmd(["cmd1", "cmd2", "cmd3"]) # Returns combined output with errors
|
|
|
|
# Check exit codes in response
|
|
result = cmd("might-fail")
|
|
if "exit code" in result.lower():
|
|
# Handle error
|
|
...
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. **Use DAG for dependencies**: Let the executor handle ordering
|
|
2. **Parallelize independent operations**: Use `parallel=True` for speed
|
|
3. **Set appropriate timeouts**: Avoid blocking on hung processes
|
|
4. **Use `ps` for long tasks**: Don't wait for builds, check later
|
|
5. **Prefer `cmd` over raw shell**: Better error handling and logging
|