A declarative tmux session manager with a tmux-compatible CLI.
ntmux is a Go-based CLI tool that wraps tmux:
- If you run it in a directory that contains
ntmux.json,ntmux.yaml, orntmux.yml, it applies that template. - Otherwise, it behaves like
tmux(pass-through), with a small convenience default: runningntmuxwith no args creates a new session named after your current directory.
⚠️ Development Status: This project is currently in active development. APIs, commands, and configuration formats may change in future releases.
- Declarative configuration: Define sessions, windows, and startup commands in JSON/YAML
- Safe re-runs (session-level): Existing sessions are skipped (ntmux does not reconcile/update them)
- Template System: Create reusable templates for different projects or workflows
- Fast apply: Batches multiple tmux operations into a single
tmuxinvocation - Editor schema support (JSON):
schema.jsonenables IDE autocomplete/validation (no runtime schema validation) - tmux compatibility: If the first argument isn’t an
ntmuxsubcommand, arguments are passed through totmux
- Go 1.23.4+ (for building from source)
- tmux (must be installed and available in PATH)
- A shell:
- On Unix, ntmux uses
$SHELL(falls back to/bin/sh). - On Windows, you’ll typically want to run under WSL, or ensure
$SHELLpoints topwsh/PowerShell.
- On Unix, ntmux uses
go install github.com/coeeter/ntmux@latest# Clone the repository
git clone https://github.com/coeeter/ntmux.git
cd ntmux
# Install using Make (installs to $(go env GOPATH)/bin)
make install
# Or build to ./tmp/ntmux
make buildntmux --helpCreate ntmux.json in your project directory:
{
"$schema": "https://raw.githubusercontent.com/coeeter/ntmux/main/schema.json",
"sessions": [
{
"name": "my-project",
"default": true,
"windows": [
{
"name": "editor",
"cmd": "nvim .",
"default": true
},
{
"name": "terminal"
},
{
"name": "server",
"cmd": "npm run dev"
}
]
}
]
}# If ntmux.json or ntmux.yaml exists in the current directory
ntmux
# Or explicitly specify the file
ntmux apply ntmux.jsonntmux will create any missing sessions from the template and attach to the default session.
ntmux supports both JSON and YAML formats:
- Auto-discovered (when running
ntmuxwith no args):ntmux.json,ntmux.yaml,ntmux.yml(checked in that order) - Supported when passed explicitly:
.json,.yaml,.yml
{
"$schema": "https://raw.githubusercontent.com/coeeter/ntmux/main/schema.json",
"sessions": [
{
"name": "session-name",
"dir": "/path/to/directory",
"default": true,
"windows": [
{
"name": "window-name",
"dir": "/path/to/directory",
"cmd": "command to run",
"default": true
}
]
}
]
}| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Unique identifier for the tmux session |
dir |
string | No | Working directory (defaults to current directory) |
default |
boolean | No | If true, attach to this session on startup |
windows |
array | Yes | Array of window configurations |
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Name of the window |
dir |
string | No | Window-specific working directory |
cmd |
string | No | Command to execute when window opens |
default |
boolean | No | If true, select this window when session starts |
- Default session: If no session has
default: true, the first session is treated as default (ntmux will attach to it). - Default window: If no window has
default: true, the first window is selected. - At least one window per session: Each session must define at least one window (ntmux uses the first window to create the session).
- Directory resolution:
- If a session
diris omitted, it defaults to your current working directory. - If a window
diris omitted, it defaults to the sessiondir. - Relative
dirpaths are resolved relative to your current working directory (not relative to the sessiondir). ~is not expanded (use an absolute path or rely on your shell).
- If a session
# If ntmux.json, ntmux.yaml, or ntmux.yml exists in current directory, apply it:
ntmuxWhen run with no arguments:
- If
ntmux.json,ntmux.yaml, orntmux.ymlexists in the current directory, ntmux applies it. - Otherwise, ntmux runs
tmux new-session -s <current-directory-name>.
# Apply default configuration file
ntmux apply
# Apply specific configuration file
ntmux apply path/to/template.json
ntmux apply path/to/template.yaml
ntmux apply path/to/template.yml# Kill all sessions defined in the template (if they exist)
ntmux stop
# Or explicitly specify the file
ntmux stop path/to/template.json# Generate JSON template (default)
ntmux new-template
# Generate YAML template
ntmux new-template --format yaml
ntmux new-template -f yamlThe new-template command will:
- Check for custom templates in
~/.config/ntmux/template.{json,yaml,yml} - Use custom template if found, otherwise use built-in defaults
- Create
ntmux.jsonorntmux.yamlin the current directory (fails if it already exists)
ntmux ships Cobra’s default completion command:
ntmux completion zsh
ntmux completion bash
ntmux completion fish
ntmux completion powershell# If the first argument isn't an ntmux subcommand, args are passed to tmux
ntmux list-sessions
ntmux attach -t my-session
ntmux kill-session -t old-session# Show combined ntmux and tmux help
ntmux --help
ntmux -h{
"$schema": "https://raw.githubusercontent.com/coeeter/ntmux/main/schema.json",
"sessions": [
{
"name": "my-app",
"windows": [
{
"name": "editor",
"cmd": "nvim .",
"default": true
},
{
"name": "terminal"
}
]
}
]
}{
"$schema": "https://raw.githubusercontent.com/coeeter/ntmux/main/schema.json",
"sessions": [
{
"name": "fullstack",
"default": true,
"windows": [
{
"name": "backend",
"dir": "./server",
"cmd": "npm run dev",
"default": true
},
{
"name": "frontend",
"dir": "./client",
"cmd": "npm start"
},
{
"name": "database",
"cmd": "docker-compose up postgres"
},
{
"name": "logs",
"cmd": "tail -f ./logs/app.log"
}
]
}
]
}{
"$schema": "https://raw.githubusercontent.com/coeeter/ntmux/main/schema.json",
"sessions": [
{
"name": "api",
"dir": "~/projects/api",
"default": true,
"windows": [
{
"name": "code",
"cmd": "nvim .",
"default": true
},
{
"name": "server",
"cmd": "go run main.go"
}
]
},
{
"name": "frontend",
"dir": "~/projects/web",
"windows": [
{
"name": "code",
"cmd": "code ."
},
{
"name": "dev",
"cmd": "npm run dev"
}
]
}
]
}sessions:
- name: my-project
dir: ~/projects/awesome-app
default: true
windows:
- name: editor
cmd: nvim .
default: true
- name: terminal
- name: server
cmd: npm run devCreate a custom template that will be used by ntmux new-template:
# Create config directory
mkdir -p ~/.config/ntmux
# Create your custom template
cat > ~/.config/ntmux/template.json << 'EOF'
{
"$schema": "https://raw.githubusercontent.com/coeeter/ntmux/main/schema.json",
"sessions": [
{
"name": "project",
"windows": [
{
"name": "nvim",
"cmd": "nvim .",
"default": true
},
{
"name": "shell"
},
{
"name": "git",
"cmd": "git status"
}
]
}
]
}
EOFNow ntmux new-template will use your custom template.
ntmux checks if a session already exists before creating it:
# First run: creates sessions
ntmux apply
# Second run: skips existing sessions (no duplicates, no updates)
ntmux applyIf you change windows/commands for an existing tmux session, ntmux will not update that session; you’ll need to kill it first (or create a new session name).
ntmux uses your $SHELL (Unix) to wrap window commands and keep the window interactive after the command finishes:
- Unix shells: Commands wrapped with
shell -c 'cmd; exec shell' - PowerShell: Commands wrapped with
pwsh -NoExit -Command "& {cmd}" - cmd.exe: Commands wrapped with
cmd /K "cmd"
The $schema field enables:
- IDE autocomplete for configuration fields
- Real-time validation in editors like VS Code
- Inline documentation for field descriptions
make help # Show available targets
make build # Build binary to ./tmp/ntmux
make install # Install to $GOPATH/bin/ntmux
make uninstall # Remove from $GOPATH/bin
make test # Run tests
make test-verbose # Run tests with verbose output
make test-cover # Generate coverage report
make clean # Remove build artifacts
make run ARGS="args" # Run with arguments
make fmt # Format code with go fmt
make vet # Run go vet
make deps # Download dependencies
make tidy # Tidy go.mod and go.sum
make check # Run fmt, vet, and tests
make generate-schema # Regenerate schema.jsonContributions are welcome! Here's how you can help:
- Check existing issues to avoid duplicates
- Provide clear description of the problem
- Include steps to reproduce
- Share your configuration file (if applicable)
- Include tmux and ntmux versions
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Run quality checks (
make check) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
# Clone your fork
git clone https://github.com/your-username/ntmux.git
cd ntmux
# Install dependencies
make deps
# Build and test
make build
make test
# Install locally for testing
make install- Follow Go best practices and idioms
- Add tests for new features
- Update documentation for user-facing changes
- Run
make checkbefore committing - Keep commits focused and atomic
Error: exec: "tmux": executable file not found in $PATH
Solution: Install tmux using your package manager:
# macOS
brew install tmux
# Ubuntu/Debian
sudo apt-get install tmux
# Fedora
sudo dnf install tmux
# Arch Linux
sudo pacman -S tmuxntmux automatically skips existing sessions. To recreate a session:
# Kill the existing session first
tmux kill-session -t session-name
# Then apply your configuration
ntmux applyEnsure your commands are properly quoted in JSON:
{
"cmd": "echo 'hello world'" // Correct
}For complex commands, use shell scripts:
{
"cmd": "./scripts/setup.sh"
}Ensure the $schema field is at the top of your configuration:
{
"$schema": "https://raw.githubusercontent.com/coeeter/ntmux/main/schema.json",
"sessions": [...]
}VS Code should automatically fetch the schema. For other editors, check their JSON schema configuration.
ntmux does not expand ~ in dir fields. Use absolute paths or keep paths relative to the directory you run ntmux from.
This project is open source. Please check the repository for license details.
Created by Coeeter
- Built with Cobra for CLI framework
- Uses invopop/yaml for YAML support
- JSON Schema generation via invopop/jsonschema