@snowy117/pi-tmux-bash
v0.0.17
Published
Drop-in bash replacement that runs commands in the background using tmux. Forked from @richardgill/pi-tmux-bash with support for non-git directories.
Readme
pi-tmux-bash
Drop-in bash replacement that runs commands in the background using tmux.
Demo
How it works
- All
bashcalls happen inside tmux - Foreground
bashtimeouts keep running in background (or kill) - Background
bashsends a follow-up result when the command finishes. - Model can enable polling to receive periodic updates on background output.
- Model can
tmux:peekto see latest background output. - Model can
tmux:killto kill managed tmux windows. - Output matches pi's built-in
bashtool (enforced with e2e tests which check against vanilla pi)
Fork notice: This is a fork of
@richardgill/pi-tmux-bashwith one key addition: it no longer requires a git repository. When not inside a git repo, it falls back to the current working directory for tmux session naming, window scoping, and the working directory of created tmux windows. SetallowNonGitDirectories: falseintmux-bash.jsoncto restore the original behavior.
Install
pi install git:github.com/Snowy117/pi-tmux-bashThen:
pi 'Run: for i in $(seq 1 90); do echo "$i"; sleep 1; done. Poll every 10s.'Or try it out without installing:
pi -e git:github.com/Snowy117/pi-tmux-bash 'Run: for i in $(seq 1 90); do echo "$i"; sleep 1; done. Poll every 10s.'See Configuration for defaults and overrides.
bash tool
Runs all bash commands in a tmux window. If a foreground bash command hits a timeout, either leave it running in the background or kill it.
{
"command": "pnpm test",
"name": "test",
"timeout": 30,
"timeoutAction": "background" // or "kill"
}Run a bash command in the background and return immediately, with optional polling for periodic output check-ins.
{
"command": "pnpm dev",
"name": "dev-server",
"background": true,
"pollInterval": 10,
"pollLines": 40
}tmux tool
The tmux tool allows the model to inspect running bash processes.
List background bash tmux windows.
{ "action": "list" }List tmux windows with polling enabled.
{ "action": "list-polls" }Capture output from one window.
{ "action": "peek", "window": "@123" }Load unfiltered tee output (when details look missing).
Works with a live/finished window id (session-indexed) and/or an absolute .out path under outputDir:
{ "action": "raw", "window": "@123" }
{ "action": "raw", "path": "/tmp/pi-bg-jobs/.../....out" }
{ "action": "raw", "window": "@123", "path": "/tmp/pi-bg-jobs/.../....out" }Prefer this (or the read tool on the path) over re-running non-idempotent commands.
Kill one window by stable tmux #{window_id}.
{ "action": "kill", "window": "@123" }Wait for one background window to finish.
{ "action": "wait", "window": "@123" }Blocks the current turn until the window finishes, up to maxTimeoutSeconds + 1s. If it finishes in time, the result is delivered automatically as a follow-up by the completion watcher (no need to peek); this action just confirms completion. If it is still running when the time elapses, it stays in the background and the result will be reported when it eventually finishes.
Start periodic output check-ins for a window.
{ "action": "poll", "window": "@123", "pollInterval": 10, "pollLines": 40 }Stop periodic output check-ins for a window.
{ "action": "unpoll", "window": "@123" }You can limit available tmux actions and bash-started polling:
{
"tmuxEnabledActions": ["peek", "kill"],
"bashPollIntervalEnabled": false
}shell tool
Use shell for commands that need a persistent PTY or stdin, such as an interactive bash, a
REPL, or a command that pauses for input. The first call starts a session and returns a
sessionId; subsequent write calls send literal input or control signals and return only new
output. The start command is a Bash command; keep it concise and prefer later write calls over
complex Bash syntax.
{
"action": "start",
"command": "bash --noprofile --norc -i",
"waitMs": 1000
}{
"action": "write",
"sessionId": "sh_0123456789ab",
"input": "echo hello\n",
"waitMs": 1000
}Use an empty input to poll without writing, or set signal to SIGINT, EOF, or SIGTERM.
Sessions are scoped to the current Pi session and are terminated when that session shuts down.
Configuration
You can override individual settings in tmux-bash.jsonc.
The default location is ~/.pi/agent/tmux-bash.jsonc, or $PI_EXTENSION_CONFIG_DIR/tmux-bash.jsonc when set.
Default config settings:
{
// ─────────────────────────────────────────────────────────────
// Bash tool settings
// ─────────────────────────────────────────────────────────────
// Default seconds to wait in foreground bash tool before applying timeoutAction (background or kill).
"defaultTimeoutSeconds": 30,
// Default action when a foreground bash command hits timeout.
"defaultTimeoutAction": "background", // "background" (default) | "kill"
// Maximum allowed bash-in-tmux timeout; higher values are capped here.
"maxTimeoutSeconds": 60,
// Milliseconds between streaming foreground bash output updates.
"foregroundBashUpdateIntervalMs": 250,
// ─────────────────────────────────────────────────────────────
// System prompt customization
// ─────────────────────────────────────────────────────────────
// Bash tool name exposed to the agent. Change if another extension registers "bash".
"bashToolName": "bash",
// Whether to register the persistent interactive shell tool.
"interactiveShellEnabled": true,
// Interactive shell tool name exposed to the agent.
"shellToolName": "shell",
// Milliseconds to wait for initial/new output before returning from a shell call.
"shellDefaultWaitMs": 1000,
// Maximum allowed shell waitMs value.
"shellMaxWaitMs": 10000,
// Tmux inspection/control tool name exposed to the agent.
"tmuxToolName": "bg_jobs",
// Tmux actions exposed to the agent. Set [] to disable registering the tmux tool.
"tmuxEnabledActions": ["list", "peek", "raw", "kill", "wait"],
// Whether bash exposes pollInterval/pollLines and can start polling from a bash call.
"bashPollIntervalEnabled": false,
// Template variables:
// `{{bashToolName}}`: configured with `bashToolName`, default `bash`
// `{{shellToolName}}`: configured with `shellToolName`, default `shell`
// `{{tmuxToolName}}`: configured with `tmuxToolName`, default `bg_jobs`
// `{{defaultTimeoutSeconds}}` / `{{defaultTimeoutAction}}` / `{{maxTimeoutSeconds}}`
// `{{bashContextLines}}` / `{{maxOutputKb}}`
// Bash tool description sent to the model tool schema.
// Supports the same template variables as systemPromptGuidelines below.
"bashToolDescription": "Execute a bash command in a background window. Output is truncated to last {{bashContextLines}} lines or {{maxOutputKb}}KB. Defaults to a {{defaultTimeoutSeconds}}s timeout, max {{maxTimeoutSeconds}}s; timeoutAction defaults to \"{{defaultTimeoutAction}}\". Use background for long-running commands.",
// Tmux tool description sent to the model tool schema.
// Supports the same template variables as systemPromptGuidelines below.
"tmuxToolDescription": "Inspect and control background jobs created by bash. Peek output is compact by default. Use action raw to load unfiltered tee output by window id or .out path.",
// Interactive shell tool description sent to the model tool schema.
// Supports the same template variables as systemPromptGuidelines below.
"shellToolDescription": "Run a Bash command in a persistent tmux PTY. Use action start with a concise Bash command, then action write with its sessionId to send input or poll output. Prefer simple commands over complex Bash syntax. Use signal for Ctrl-C, Ctrl-D, or termination.",
// modify Pi's built-in system prompt.
"systemPrompt": true,
// Tool snippets for Pi's generated system prompt tools section.
"bashSystemPromptSnippet": "Execute bash commands in background windows", // string | false (to disable)
"tmuxSystemPromptSnippet": "Inspect and control the background jobs created by bash tool", // string | false (to disable)
"shellSystemPromptSnippet": "Run interactive shell processes and continue them with the shell session id", // string | false (to disable)
// Guideline bullets appended to Pi's generated system prompt:
// Omit systemPromptGuidelines to use defaults.
// [] to disable tmux-bash guidelines.
"systemPromptGuidelines": [
"Use {{bashToolName}} with background: true or timeoutAction: \"background\" for long-running commands, servers, watchers, and background bash commands.",
"Use {{shellToolName}} for REPLs and interactive prompts that need stdin or a persistent PTY.",
"Background bash commands will report automatically when they finish; do not keep polling manually unless you need interim output.",
"Use {{tmuxToolName}} list to find background windows",
"Use {{tmuxToolName}} peek/kill with a stable #{window_id} like @123.",
"If asked, tell the user the background window id (e.g. @123) and they will know how to view it live.",
"If a background command's completion is missing, its full output is saved in a .out file under {{outputDir}}; recover it with `find {{outputDir}} -name '*.out'` then read.",
"Use {{tmuxToolName}} wait to block the current turn until a background window finishes. It waits up to {{maxTimeoutSeconds}}s; if the task finishes in time, its result is delivered automatically as a follow-up — no need to peek or poll.",
"When bash/completion output includes a raw .out path (or looks over-compressed/missing details), recover unfiltered output with {{tmuxToolName}} raw (window id and/or path) or the read tool on that path — do not re-run non-idempotent commands."
],
// ─────────────────────────────────────────────────────────────
// Tmux settings
// ─────────────────────────────────────────────────────────────
// When not inside a git repository, fall back to the current working directory instead of
// erroring with "not in a git repository". Set to false to restore the original behavior
// (bash/tmux tools refuse to run outside a git repo).
"allowNonGitDirectories": true, // true (default) | false
// Use a global tmux session, or a per-git-root tmux session.
"tmuxSessionScope": "global", // "global" (default) | "git-root"
// Background tmux session name when tmuxSessionScope is "global".
"globalTmuxSessionName": "pi-background",
// Template for the background tmux session name when tmuxSessionScope is "git-root".
// "{{gitRootSessionName}}" is replaced with the normal git-root session name.
"gitRootTmuxSessionNameTemplate": "{{gitRootSessionName}}-bg",
// Which windows inside the selected tmux session list/peek/kill/poll commands can access.
"tmuxWindowScope": "pi-session", // "pi-session" (default) | "git-root" | "all"
// Template for created tmux window names.
// Supports {{nameOrCommand}}, {{name}}, and {{command}}.
"tmuxWindowNameTemplate": "{{nameOrCommand}}",
// Maximum tmux window name length.
"maxTmuxWindowNameLength": 30,
// Kill tmux windows after bash command completes.
"autoCloseWindowsOnCompletion": true, // true (default) | false
// tmux binary/path used for all tmux invocations.
"tmuxBinary": "tmux",
// ─────────────────────────────────────────────────────────────
// Polling and output limits
// ─────────────────────────────────────────────────────────────
// Default seconds between automatic poll check-ins. 0 disables default polling.
// Ignored by bash calls when bashPollIntervalEnabled is false.
"defaultPollInterval": 0,
// Whether poll cards trigger model turns or display only in the TUI.
"pollDelivery": "model", // "model" (default) | "display"
// Minimum seconds between model-delivered poll turns. Does not throttle display-only polls.
"minimumPollIntervalSeconds": 10,
// Maximum output bytes kept for model context and TUI cards.
"maxOutputBytes": 51200,
// Foreground bash output lines sent to model context.
"bashContextLines": 2000,
// Max characters of the command shown in the TUI tool-call title. Set to 0 to show the full command.
"bashCommandDisplayLength": 80,
// Max lines of the command shown in the TUI tool-call title when collapsed. When the command
// exceeds this many lines, only the first N lines are shown followed by "...".
"bashCommandCollapsedDisplayLines": 2,
// Replacement character shown in place of unsafe control bytes, lone escape introducers,
// and invisible Unicode (bidi overrides, zero-width, BOM) found in bash output.
"controlCharFallback": ".",
// Theme color name used for the control-character fallback marker.
"controlCharFallbackColor": "error",
// Completed background command lines sent to model context.
"completedContextLines": 20,
// Poll output lines sent to model context.
"pollContextLines": 30,
// Peek output lines sent to model context.
"peekContextLines": 2000,
// Foreground bash output lines shown in compact TUI cards.
"bashCompactDisplayLines": 5,
// Foreground bash output lines shown in compact TUI cards when output is truncated.
"bashTruncatedCompactDisplayLines": 2,
// Foreground bash output lines shown in expanded/uncompacted TUI cards.
"bashExpandedDisplayLines": 2000,
// Completed background command lines shown in compact TUI cards.
"completedCompactDisplayLines": 5,
// Completed background command lines shown in compact TUI cards when output is truncated.
"completedTruncatedCompactDisplayLines": 2,
// Completed background command lines shown in expanded/uncompacted TUI cards.
"completedExpandedDisplayLines": 20,
// Poll output lines shown in compact TUI cards.
"pollCompactDisplayLines": 5,
// Poll output lines shown in compact TUI cards when output is truncated.
"pollTruncatedCompactDisplayLines": 2,
// Poll output lines shown in expanded/uncompacted TUI cards.
"pollExpandedDisplayLines": 30,
// Peek output lines shown in compact TUI cards.
"peekCompactDisplayLines": 5,
// Peek output lines shown in compact TUI cards when output is truncated.
"peekTruncatedCompactDisplayLines": 2,
// Peek output lines shown in expanded/uncompacted TUI cards.
"peekExpandedDisplayLines": 2000,
// ─────────────────────────────────────────────────────────────
// Advanced settings
// ─────────────────────────────────────────────────────────────
// Hides wrapper/shim lines from the displayed command by showing only lines after the last marker.
// Set to "" to disable.
"displayCommandStartMarker": "# SHIM_END", // use "" to disable
// Show the .out file path even when output is not truncated.
"alwaysShowOutputFilePath": false, // true | false (default)
// Keep .out files on pi shutdown instead of deleting the signal/output dir.
"preserveOutputFiles": true, // true (default) | false
// Base directory for per-session signal files, generated scripts, and .out files.
"outputDir": "/tmp/pi-bg-jobs",
// Environment names not exported from Pi into bash-in-tmux scripts.
// Skips shell/tmux bookkeeping that should be owned by the new tmux window.
"tmuxEnvExportDenylist": ["PWD", "OLDPWD", "SHLVL", "_", "TMUX", "TMUX_PANE"],
// ─────────────────────────────────────────────────────────────
// Environment variables for commands in tmux
// ─────────────────────────────────────────────────────────────
// Custom environment variables to set in every bash-in-tmux script.
// These are exported after the Pi process environment, so they override any
// conflicting values from the parent process. Defaults are designed to make
// command output more predictable and machine-readable for the LLM:
// NO_COLOR=1 — disable ANSI color escape sequences
// TERM=dumb — minimal terminal capability (no cursor movement)
// PAGER=cat — avoid pager prompts (e.g. from git log)
// LC_ALL=C — stable locale, forces English output
// DEBIAN_FRONTEND=noninteractive — suppress apt prompts
//
// Set to {} to inherit the Pi process environment as-is.
"tmuxEnv": {
"NO_COLOR": "1",
"TERM": "dumb",
"PAGER": "cat",
"LC_ALL": "C",
"DEBIAN_FRONTEND": "noninteractive"
}
}API helpers
Other extensions can import tmux-bash helpers to target the same background tmux sessions and scoped windows.
loadTmuxBashConfig
Reads tmux-bash.jsonc from the extension config folder (PI_EXTENSION_CONFIG_DIR, then Pi's agent directory).
import { loadTmuxBashConfig } from "@richardgill/pi-tmux-bash/core";
const options = loadTmuxBashConfig();resolveTmuxBashContext
Resolves the current git root, configured tmux session, and scoped window filters.
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
import { loadTmuxBashConfig, resolveTmuxBashContext } from "@richardgill/pi-tmux-bash/core";
const options = loadTmuxBashConfig();
const context = resolveTmuxBashContext(ctx, options);
if (!context) ctx.ui.notify("Not in a git repository.", "error");listBashWindows
Lists bash-created tmux windows matching the resolved scope.
import { listBashWindows, resolveTmuxBashContext } from "@richardgill/pi-tmux-bash/core";
const options = loadTmuxBashConfig();
const context = resolveTmuxBashContext(ctx, options);
const windows = context ? listBashWindows(context) : [];
// [{ id: "@2172", index: 3, title: "hello-sleep-done", outputFile: "/tmp/..." }]Read the active background count from footer status
Tmux-bash publishes the active background count with Pi's status API. Footer extensions can read it from footerData.getExtensionStatuses() and handle their own string formatting.
const backgroundBashStatusKey = "backgroundBashTmuxCommands";
const formatBackgroundBashStatus = (value: string) =>
`${value} background proc${value === "1" ? "" : "s"}`;
ctx.ui.setFooter((_tui, theme, footerData) => ({
invalidate() {},
render(width: number): string[] {
const status = footerData.getExtensionStatuses().get(backgroundBashStatusKey);
const backgroundBashStatus = status ? formatBackgroundBashStatus(status) : "";
return [theme.fg("dim", backgroundBashStatus)];
},
}));The status key is backgroundBashTmuxCommands. Status values are strings; tmux-bash clears the status when there are no active background windows.
Credits
This extension was inspired by indigoviolet/pi-tmux.

