@richardgill/pi-tmux-bash

Drop-in `bash` replacement that runs commands in the background using tmux.

Packages

Package details

extension

Install @richardgill/pi-tmux-bash from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:@richardgill/pi-tmux-bash
Package
@richardgill/pi-tmux-bash
Version
0.0.12
Published
Jun 6, 2026
Downloads
1,293/mo · 232/wk
Author
richardgill
License
unknown
Types
extension
Size
108.6 KB
Dependencies
4 dependencies · 3 peers
Pi manifest JSON
{
  "extensions": [
    "./src/index.ts"
  ],
  "video": "https://raw.githubusercontent.com/richardgill/pi-extensions/main/extensions/tmux-bash/demo/demo.mp4"
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

pi-tmux-bash

Drop-in bash replacement that runs commands in the background using tmux.

Demo

tmux-bash demo

Watch demo video

How it works

  • All bash calls happen inside tmux
  • Foreground bash timeouts keep running in background (or kill)
  • Background bash sends a follow-up result when the command finishes.
  • Model can enable polling to receive periodic updates on background output.
  • Model can tmux:peek to see latest background output.
  • Model can tmux:kill to kill managed tmux windows.
  • Output matches pi's built-in bash tool (enforced with e2e tests which check against vanilla pi)

Install

pi install npm:@richardgill/pi-tmux-bash

Then:

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 npm:@richardgill/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" }

Kill one window by stable tmux #{window_id}.

{ "action": "kill", "window": "@123" }

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
}

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",

  // Tmux inspection/control tool name exposed to the agent.
  "tmuxToolName": "tmux",

  // Tmux actions exposed to the agent. Set [] to disable registering the tmux tool.
  "tmuxEnabledActions": ["list", "peek", "kill"],

  // Whether bash exposes pollInterval/pollLines and can start polling from a bash call.
  "bashPollIntervalEnabled": false,

  // Template variables:
  // `{{bashToolName}}`: configured with `bashToolName`, default `bash`
  // `{{tmuxToolName}}`: configured with `tmuxToolName`, default `tmux`
  // `{{attachCommand}}`:
  //   Uses `tmux switch-client -t @123` when Pi is already inside tmux. Otherwise `tmux attach -t @123`.
  //   Uses configured `tmuxBinary`.
  // `{{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 tmux 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 tmux windows created by bash.",

  // 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 tmux windows", // string | false (to disable)
  "tmuxSystemPromptSnippet": "Inspect and control the background tmux sessions created by bash tool", // 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, REPLs, interactive prompts, and background bash commands.",
    "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, you can attach to tmux window using: {{attachCommand}}, where @123 is a #{window_id}."
  ],

  // ─────────────────────────────────────────────────────────────
  // Tmux settings
  // ─────────────────────────────────────────────────────────────

  // 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,

  // 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-tmux-bash",

  // 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"]
}

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.