Skip to content

Repository files navigation

better-bash

better-bash

Background process management for LLM coding agents
Non-blocking bash with streaming output, stdin, and long-polling

npm license tests ci size


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 build

What 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 endpoints

Monitor 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
  • $HOME expands, 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 typecheck

License

MIT

About

Background process management for LLM coding agents — non-blocking bash with streaming output, stdin, and long-polling

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages