npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@infodb/booth

v0.6.0

Published

Launch and drive claude sessions inside tmux on a docker compose service (devenviron)

Readme

@infodb/booth

A CLI that opens a booth — a tmux session on a docker compose service — and starts an interactive command (typically claude) inside it.

The command keeps running after booth open returns. Later you talk to it with booth send, read it with booth logs, sit in it with booth attach, and shut it down with booth close, which sends /exit before killing the session.

Everything runs through docker compose exec <service> tmux ..., so booth itself holds no state: the tmux server in the container is the source of truth.

Running it

Nothing to install — pnpx fetches the published package:

pnpx @infodb/booth ls

If you use it often, either alias it or install it globally:

alias booth='pnpx @infodb/booth'
# or
pnpm add -g @infodb/booth

booth drives docker compose, so it has to run where the compose project is: on the host, or inside a devenviron container (the docker socket is bind mounted there, and node is already present). The examples below assume booth resolves to one of the forms above.

Quick start

booth init             # write a sample booth.toml
booth targets          # show the configured targets
booth open myproject   # start claude in /workspaces/myproject as session 'myproject'
booth ls
booth send myproject "run the tests and report back"   # waits, stops if it needs you
booth status myproject
booth logs myproject
booth close myproject

Naming

A booth name is a folder name under workspaces_root. booth open myproject starts the command in /workspaces/myproject as session myproject. Because folder names are unique within the workspaces root, session names cannot collide.

tmux does not allow . or : in a session name and silently rewrites them to _, so a booth named repo.branch (the usual shape for a git worktree) is repo_branch on the tmux side. Always pass the booth name to booth's own commands; ls reports it that way too.

Configuration

booth reads booth.toml, resolved in this order:

  1. --config <path>
  2. $BOOTH_CONFIG
  3. booth.toml in the current directory or any parent
  4. ~/.config/booth/booth.toml

If none is found, every command except init fails with an error and exits 1 — booth never guesses a target, so nothing is started. booth init writes the shipped booth.example.toml into the current directory.

[defaults]
# Used when --target is omitted. Optional if exactly one target is defined.
target = "denv"
# Booth names are folders under this directory, and the tmux session name.
workspaces_root = "/workspaces"
# The command started inside tmux. {name} and {workdir} are expanded.
# --remote-control starts an interactive session with Remote Control enabled,
# not the server mode (the `claude remote-control` subcommand).
command = "claude --remote-control {name}"
# Add --continue at startup so the booth picks up the previous conversation in its
# workdir. Defaults to true; set it to false to always start fresh.
continue = true

# One shared container hosting every booth.
[targets.denv]
compose_file = "/workspaces/.devcontainer/denv-cc-remote/docker-compose.yaml"
service = "denv"
# project = "denv-cc-remote"   # passed as `docker compose -p` when set

# One container per project: `service` expands {name} to the booth name.
# [targets.perproject]
# compose_file = "/workspaces/.devcontainer/denv-cc-remote/docker-compose.yaml"
# service = "{name}"

# Only booths that need an override have to be listed.
[booths.myproject]
target = "denv"
command = "claude --remote-control myproject --add-dir /workspaces/shared"
continue = false

Both container layouts work. With service = "{name}" each booth gets its own container, and booth ls asks compose for the running services and queries each of them (services without tmux are skipped). With a fixed service name, every booth is a tmux session in one container.

compose_file may be relative; it is resolved against the directory of booth.toml. command accepts a string (split on whitespace) or an array of strings. tmux joins the arguments back into one shell command, so avoid embedded quoting and write a wrapper script if you need it.

Continuing the previous conversation

By default booth open starts the command with --continue, so reopening a booth picks up the conversation it had in that workdir instead of starting from nothing — closing a booth for the night and opening it again in the morning keeps the thread.

The first time a workdir is opened there is nothing to continue, and claude --continue exits instead of starting. booth notices that the session died on startup and reopens it without --continue, reporting No conversation to continue; started a fresh one. So the default is safe on a brand new project.

Override it per run with booth open <name> --no-continue (or --continue when the config turns it off), per booth with [booths.<name>].continue, or globally with [defaults].continue. booth never adds the flag twice: if command already contains -c, --continue, -r or --resume, it is left alone. Note that -c on the booth command itself is --config; the resume switch is spelled out as --continue.

Commands

| Command | Description | | --- | --- | | booth init [--force] | Write a sample booth.toml into the current directory | | booth targets | List the targets defined in the config | | booth open <name> [--restart] [--no-continue] [--no-wait] [-a] [--ready-timeout s] | Create the tmux session, start the command, and wait until it is actually usable (-a/--attach: attach right after it starts) | | booth ls [--target t] | List sessions with their state | | booth status <name> [--json] [--pane n] [--wait-for settled] | Report the state and exit with a code that matches it | | booth send <name> <text...> [--no-wait] [-w seconds] [--pane n] [-f] | Send a line, then wait for the turn to finish — or stop as soon as the session needs you | | booth logs <name> [-n lines] | Print the current pane content | | booth attach <name> | Attach interactively (detach with the usual Ctrl-b d) | | booth close <name> [--settle s] [--wait s] [--force] | Wait for the turn to finish, send /exit, then kill the session |

open refuses to reuse a live session unless --restart is given, checks that the workdir exists in the container, and fails loudly if the command dies during startup instead of reporting a session that is already gone. With --attach it hands the terminal over as soon as the command has started, without waiting for it to become ready, so a login screen or trust dialog can be answered on the spot; if the session is already open (and --restart is not given) it simply attaches to it.

States and feedback

The point of booth is not to fire commands into the dark. Every command that waits stops the moment the session needs a human — and says so, with the pane attached — so whoever (or whatever) is driving can react.

The state comes from claude itself, via claude agents --json inside the container, so it is not screen scraping. There are three tiers:

| State | Meaning | Exit code | | --- | --- | --- | | not open | No tmux session | 13 | | starting | The session exists but claude has not registered yet — it is booting, or stopped at the login screen or the trust dialog (booth tells you which) | 12 | | idle | At the prompt, ready for input | 0 | | busy | Working on a turn | 10 | | waiting | Stopped on something a human must answer — a dialog, a permission prompt. waitingFor says what | 11 | | no status | The booth does not run claude, so there is nothing to report | 0 |

booth send waits for the turn to finish and exits 0. If the session stops on a dialog instead, it exits 11 and prints the pane. Sending text into an open dialog would go nowhere, so send refuses to do it unless you pass --force. booth close waits for the current turn to finish before sending /exit, because a /exit typed mid-turn is dropped.

booth send myproject "run the tests"    # → 0: turn finished · 11: needs you · 10: still busy
booth status myproject --json           # machine-readable state for a supervising process

Skill for Claude Code

skill/SKILL.md teaches a Claude Code session the lifecycle — open, send, close — and the exit-code contract. It deliberately stops there: when booth stops on something, the CLI itself prints the commands to run next, so that knowledge lives in the tool rather than in a document that can drift. Install it once:

mkdir -p ~/.claude/skills/booth
cp "$(pnpm root -g)/@infodb/booth/skill/SKILL.md" ~/.claude/skills/booth/SKILL.md

Under devenviron, ~/.claude is bind mounted from .devcontainer/denv/.claude, so installing it once makes it available in every container.

Container requirements

The service must have tmux installed and must stay up on its own — the interactive command is started by booth, not by the compose command:. A service that used to run claude as its main process becomes:

services:
  denv:
    image: denv-cc-remote:local
    command: ["sleep", "infinity"]

License

MIT