A TUI command runner based on git changes
Project description
Fnug
[!WARNING] The main branch is currently undergoing a refactor to Rust. If you're looking for the latest Python version, see the
pythonbranch.
Fnug is a TUI command runner that automatically selects and executes lint and test commands based on git changes or file watching. Think of it as a terminal multiplexer (like tmux), but purpose-built for running your dev commands side by side.
Features
- Git integration — automatically select commands based on uncommitted file changes
- File watching — monitor the file system and re-select commands when files change
- Terminal emulation with scrollback — full PTY support for interactive commands and long output
- Headless mode (
fnug check) — run selected commands without the TUI, useful for CI - Git hook integration (
fnug init-hooks) — install a pre-commit hook that runsfnug check - Command dependencies — define
depends_onto control execution order - Environment variables — set per-command or per-group env vars
- Nested command groups — organize commands into a hierarchical tree with inherited settings
- Workspace support — discover and merge
.fnug.yamlfiles from subdirectories in mono-repos
Installation
From crates.io
cargo install fnug
From PyPI
# With uv
uv tool install fnug
# With pipx
pipx install fnug
From GitHub Releases
Download a prebuilt binary from GitHub Releases.
With Nix
# Run directly
nix run github:nickolaj-jepsen/fnug
# Or install to profile
nix profile install github:nickolaj-jepsen/fnug
From source
git clone https://github.com/nickolaj-jepsen/fnug.git
cd fnug
cargo install --path .
Usage
Run fnug in a directory with a .fnug.yaml configuration file (or pass -c path/to/config.yaml).
Subcommands
| Command | Description |
|---|---|
fnug |
Launch the TUI |
fnug check |
Run selected commands headlessly (exit code reflects pass/fail) |
fnug init-hooks |
Install a git pre-commit hook that runs fnug check |
Flags
| Flag | Description |
|---|---|
-c <path> |
Path to config file |
--no-workspace |
Disable workspace resolution (don't search for a parent root) |
--log-file |
Write logs to a file |
--log-level |
Log level: off, error, warn, info, debug, trace (default: info) |
--fail-fast |
Stop on first failure (check only) |
--no-tui |
Never prompt to open TUI on failure (check only) |
--mute-success |
Suppress output for passing commands (check only) |
--all |
Include commands with auto.check: false (check only) |
--force |
Overwrite existing hook (init-hooks only) |
Configuration
Fnug searches for .fnug.yaml, .fnug.yml, or .fnug.json from the current directory upward.
Minimal example
fnug_version: 0.1.0
name: my-project
commands:
- name: hello
cmd: echo world
Git auto-selection
Select commands based on uncommitted changes. Re-trigger with g in the TUI.
fnug_version: 0.1.0
name: my-project
commands:
- name: lint
cmd: cargo clippy
auto:
git: true
path:
- "./src"
regex:
- "\\.rs$"
File watching
Monitor the file system and select commands when matching files change. Can be combined with git auto.
fnug_version: 0.1.0
name: my-project
commands:
- name: test
cmd: cargo test
auto:
watch: true
path:
- "./src"
regex:
- "\\.rs$"
Always auto-selection
Mark commands that should always be selected, regardless of git changes or file watching.
fnug_version: 0.1.0
name: my-project
commands:
- name: typecheck
cmd: cargo check
auto:
always: true
Excluding commands from check mode
Commands with auto.check: false are skipped during fnug check (and git hooks) but remain auto-selected in the TUI. Use fnug check --all to include them.
Useful for commands that are too slow or noisy for pre-commit checks but you still want to run them automatically in the TUI.
fnug_version: 0.1.0
name: my-project
commands:
- name: unit tests
cmd: cargo test
auto:
git: true
- name: integration tests
cmd: cargo test --release
auto:
git: true
check: false # skip in `fnug check`, still auto-selected in TUI
Nested groups with inheritance
Groups inherit cwd, auto, and env settings from their parent.
fnug_version: 0.1.0
name: my-project
children:
- name: backend
auto:
git: true
watch: true
path:
- "./src"
regex:
- "\\.rs$"
commands:
- name: fmt
cmd: cargo fmt
- name: test
cmd: cargo test
- name: clippy
cmd: cargo clippy
Workspace
Workspace mode discovers .fnug.yaml files in subdirectories and merges them as child groups. This is useful for mono-repos where each package has its own config.
When workspace: true, fnug walks the filesystem (skipping .gitignore'd and hidden directories) to find sub-configs. Files do not need to be git-tracked to be discovered.
# Auto-discover sub-configs (walks up to 5 levels deep)
fnug_version: 0.1.0
name: my-monorepo
workspace: true
commands:
- name: root-lint
cmd: echo "root"
# Custom max scan depth
workspace:
max_depth: 2
# Explicit glob patterns
workspace:
paths:
- "./packages/*/"
- "./apps/*/"
When run from a subdirectory that contains a .fnug.yaml, fnug automatically resolves upward to the nearest workspace root. Use --no-workspace to disable this behavior.
Advanced example
See this project's .fnug.yaml for a full example.
Configuration reference
Root fields
| Field | Type | Description |
|---|---|---|
fnug_version |
string | Expected fnug version — warns on mismatch |
name |
string | Display name for the root group |
workspace |
bool / object | Enable workspace mode (see Workspace) |
commands |
list | Top-level commands |
children |
list | Nested command groups |
cwd |
string | Working directory (inherited by children) |
env |
map | Environment variables (inherited by children) |
auto |
object | Default auto rules (inherited by children) |
Command fields
| Field | Type | Description |
|---|---|---|
name |
string | Display name (required) |
cmd |
string | Shell command to run (required) |
id |
string | Custom identifier — defaults to the command name |
cwd |
string | Working directory override |
env |
map | Extra environment variables |
auto |
object | Auto-selection rules (see below) |
depends_on |
list of strings | Command IDs that must finish before this one runs |
scrollback |
integer | PTY scrollback buffer size (number of lines) |
Group fields
| Field | Type | Description |
|---|---|---|
name |
string | Display name (required) |
id |
string | Custom identifier — defaults to the group name |
cwd |
string | Working directory (inherited by children) |
env |
map | Environment variables (inherited by children) |
auto |
object | Default auto rules (inherited by children) |
commands |
list | Commands in this group |
children |
list | Nested child groups |
Auto fields
| Field | Type | Description |
|---|---|---|
git |
bool | Select when git-changed files match path/regex |
watch |
bool | Select when watched files match path/regex |
always |
bool | Always selected regardless of changes |
path |
list of strings | Path prefixes to match against (e.g. "./src") |
regex |
list of strings | Regex patterns to match against file paths (e.g. "\\.rs$") |
check |
bool | Include in fnug check — set false to skip (default true) |
Keyboard Shortcuts
| Key | Context | Action |
|---|---|---|
j / ↓ |
Tree | Move down |
k / ↑ |
Tree | Move up |
h / ← |
Tree | Collapse group / Deselect command |
l / → |
Tree | Expand group / Select command |
Space |
Tree | Toggle expand/select |
Enter |
Tree | Run all selected commands |
r |
Tree | Run current command |
s |
Tree | Stop current command |
c |
Tree | Clear current command |
g |
Tree | Git auto-select |
/ |
Tree | Search/filter commands |
Esc |
Search | Clear search |
L |
Tree | Toggle log panel |
Tab |
Tree | Focus terminal |
Esc |
Terminal | Back to tree |
Ctrl+R |
Global | Toggle fullscreen |
Ctrl+C |
Global | Quit |
q |
Tree | Quit |
Mouse
- Click a tree item to select it
- Double-click a command to run it, or a group to expand/collapse
- Click the selection orb (●/○) or arrow (▼/▶) to toggle
- Drag the separator between tree and terminal to resize
- Scroll wheel in the terminal panel to scroll output
- Right-click a command for a context menu with run/stop/clear options
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fnug-0.1.0a13.tar.gz.
File metadata
- Download URL: fnug-0.1.0a13.tar.gz
- Upload date:
- Size: 763.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: maturin/1.12.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3cfa0c2889ed60f5f1883f040b8f5ef3ef8847226fa3101e320fad1554c8c09e
|
|
| MD5 |
27a89ee68b28c19f654202272fcd61ff
|
|
| BLAKE2b-256 |
138896511b207f4fc94d38e50fdd7af17c9ad23a30f6732265de9a55cabca630
|
File details
Details for the file fnug-0.1.0a13-py3-none-win_amd64.whl.
File metadata
- Download URL: fnug-0.1.0a13-py3-none-win_amd64.whl
- Upload date:
- Size: 3.7 MB
- Tags: Python 3, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: maturin/1.12.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a585fbff4958442a544dab9babd6cda93aacb4d963a223915a2064c81314955b
|
|
| MD5 |
f5b924d8b0e8afa147865bb74312972a
|
|
| BLAKE2b-256 |
63dbaa495bc61ae92a8427f146598f0b7a4d2f2c39f86c92096ff54e637f729a
|
File details
Details for the file fnug-0.1.0a13-py3-none-manylinux_2_28_x86_64.whl.
File metadata
- Download URL: fnug-0.1.0a13-py3-none-manylinux_2_28_x86_64.whl
- Upload date:
- Size: 3.5 MB
- Tags: Python 3, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: maturin/1.12.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e29834e36a28107d26d67dedceac59423d902e9bcb25dea376cb17c4e582789a
|
|
| MD5 |
fa3505baf9bf72a9000f4ac1ca6498ec
|
|
| BLAKE2b-256 |
1c2d95f565a9447d0bfc47bc56c5945bfb8eccd497114f1f853fc58caccfff0d
|
File details
Details for the file fnug-0.1.0a13-py3-none-manylinux_2_28_aarch64.whl.
File metadata
- Download URL: fnug-0.1.0a13-py3-none-manylinux_2_28_aarch64.whl
- Upload date:
- Size: 3.4 MB
- Tags: Python 3, manylinux: glibc 2.28+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: maturin/1.12.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2a2baf146e0fff51000e3accd9b9be02d0c60ee4514004bce750a276d7827122
|
|
| MD5 |
ec0bed77189859f451a63fb4bca30e02
|
|
| BLAKE2b-256 |
d0f7793359eccf6a687ea8f044f6cb8d8f6bbd72cfcef00cef2fdce2605cbd79
|
File details
Details for the file fnug-0.1.0a13-py3-none-macosx_11_0_arm64.whl.
File metadata
- Download URL: fnug-0.1.0a13-py3-none-macosx_11_0_arm64.whl
- Upload date:
- Size: 3.2 MB
- Tags: Python 3, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: maturin/1.12.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1b320b3057fcc5f336fd8478479cfacefb779006217a6f69400acffb13ea1614
|
|
| MD5 |
efad6d8ecb2ac7a0a78e2075616b1d28
|
|
| BLAKE2b-256 |
0151af282306880ff1a9882e34f9f090817411b5e14066102277eb08251ccffa
|
File details
Details for the file fnug-0.1.0a13-py3-none-macosx_10_12_x86_64.whl.
File metadata
- Download URL: fnug-0.1.0a13-py3-none-macosx_10_12_x86_64.whl
- Upload date:
- Size: 3.3 MB
- Tags: Python 3, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: maturin/1.12.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
67fdfafde1136942725c92bfa874fd0fef004f5029d7a6edb4b2f3f7e23f415d
|
|
| MD5 |
9ff7a29e8dc57d27f936e515859a801e
|
|
| BLAKE2b-256 |
affcdf7216ed0b3cbc0b40b0b976ec0c718f2fab64305a285728bf19c061313a
|