better-bash
v1.0.6
Published
Background process management for LLM coding agents — non-blocking bash with streaming output, stdin, and long-polling
Maintainers
Readme
Your agent runs npm run build and waits. Does nothing. Watches output scroll by. Wastes tokens.
better-bash fixes that. Five tools that turn blocking bash into background process management: the agent starts a command, does other work, checks back when it's ready.
Install
OpenCode
Add to ~/.config/opencode/opencode.json:
{
"plugin": ["better-bash"]
}MiMoCode
Add to ~/.config/mimocode/mimocode.json:
{
"plugin": ["better-bash"]
}From Source
git clone https://github.com/alper-dev/better-bash.git
cd better-bash
bun install && bun run buildWhat You Get
| Tool | Does |
| --- | --- |
| bash_start | Start a command in the background |
| bash_status | Check status, get new output (long-poll supported) |
| bash_kill | Kill a running process |
| bash_stdin | Write to a process's stdin |
| bash_list | List all running processes |
Examples
Without better-bash:
agent: bash("npm run build")
agent: ... waits 45 seconds doing nothing ...
agent: "Build complete!"With better-bash:
agent: bash_start("npm run build")
→ { id: "pk7qx", pid: 1234 }
agent: bash_status("pk7qx")
→ { status: "running", newOutput: "compiling 42/100..." }
agent: (reviews a PR while the build runs)
agent: bash_status("pk7qx", { wait: 5000 })
→ { status: "done", newOutput: "...built in 12s", exitCode: 0 }Run tests while fixing another file
bash_start("pytest tests/ -v")
→ { id: "pm2va", pid: 4521 }
# Agent edits src/auth.py while tests run
bash_status("pm2va", { wait: 30000 })
→ { status: "done", newOutput: "48 passed, 2 failed in 12.3s", exitCode: 1 }
# Agent sees which tests failed, fixes them
bash_status("pm2va", { all: true })
→ { newOutput: "FAILED tests/test_login.py::test_expired_token..." }Start a dev server and wait for it to be ready
bash_start("npm run dev")
→ { id: "pq9dx", pid: 8834 }
bash_status("pq9dx", { wait: 10000 })
→ { status: "running", newOutput: "Server running on http://localhost:3000" }
# Server is ready, agent can now test endpointsMonitor a database migration
bash_start("npx prisma migrate deploy")
→ { id: "pz4mn", pid: 2291 }
bash_status("pz4mn", { wait: 15000 })
→ { status: "running", newOutput: "Applying migration 20240101_add_users..." }
bash_status("pz4mn")
→ { status: "done", newOutput: "3 migrations applied successfully.", exitCode: 0 }Pipe input to an interactive script
bash_start("psql -U postgres mydb")
→ { id: "pa8kx", pid: 3301 }
bash_stdin("pa8kx", "SELECT count(*) FROM users WHERE active = true;")
bash_status("pa8kx")
→ { newOutput: " count\n-------\n 847\n(1 row)" }Stdin
Send input to running processes, useful for interactive scripts, REPLs, and CLIs:
bash_start("python manage.py shell")
→ { id: "ph3tq", pid: 6612 }
bash_stdin("ph3tq", "from django.contrib.auth.models import User")
bash_stdin("ph3tq", "User.objects.filter(is_active=False).count()")
bash_status("ph3tq")
→ { newOutput: ">>> 42" }Newline is auto-appended if missing.
Shell Quoting
PowerShell uses Base64-encoded commands. Rules:
- Use single quotes:
echo 'hello world' - Avoid double quotes unless needed; PowerShell escapes with backtick (
`), not backslash ; | & < > { }work fine for chaining$HOMEexpands, so wrap in single quotes to prevent
Stdin Compatibility
| Consumer | Works | Notes |
| --- | --- | --- |
| Python input() | Yes | Newline auto-appended |
| Python readline() | Yes | Newline auto-appended |
| Python read() | Yes | Set eof: true after optional data |
| Python readlines() | Yes | Set eof: true after optional data |
| PowerShell Read-Host | Yes | |
| PowerShell $input pipe | No | Known limitation |
API
bash_start(command, opts?)
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| command | string | required | Shell command |
| workdir | string | cwd | Working directory |
| timeout | number | 0 | Process timeout in ms; 0 disables timeout (unlimited) |
Returns: { id, pid, status }
bash_status(id, opts?)
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| id | string | required | Process ID |
| maxOutput | number | 30000 | Max bytes to return |
| wait | number | 0 | Long-poll timeout in ms |
| all | boolean | false | Reset pagination and return output from beginning |
Returns: { id, status, exitCode, newOutput, totalOutputSize, truncated, hasMore, elapsed }
Statuses: running done killed error timed_out
bash_kill(id)
Running process: "Process X terminated."
Already exited: "Process X already exited with status 'done' (exit code 0)."
bash_stdin({ id, data?, eof? })
Write optional data to stdin. Newline auto-appended if missing. Set eof: true to close stdin after data and signal EOF. Close-only call: bash_stdin({ id: "pk7qx", eof: true }).
bash_list()
Returns all processes with ID, PID, command, status, exit code, output size, elapsed time.
Development
bun install
bun test
bun run build # dist/
bun run typecheckLicense
MIT
