Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [v0.3.1] - Sep 05, 2026

### Fixed

- Stop and clean up processes that are still running when `mult` exits
Expand All @@ -33,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Change flags for delay, and "stop on first failure"

[unreleased]: https://github.com/dhth/mult/compare/v0.3.0...HEAD
[unreleased]: https://github.com/dhth/mult/compare/v0.3.1...HEAD
[v0.3.1]: https://github.com/dhth/mult/compare/v0.3.0...v0.3.1
[v0.3.0]: https://github.com/dhth/mult/compare/v0.2.0...v0.3.0
[v0.2.0]: https://github.com/dhth/mult/compare/v0.1.3...v0.2.0
264 changes: 145 additions & 119 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
Run a command multiple times and glance at the outputs.

```bash
mult -- command --you=want --to=run
mult -- curl -sSif https://flaky.service.example
```

[![usage](https://asciinema.org/a/E06efxcFkA4RSSTI7keeUtFqp.svg)](https://asciinema.org/a/E06efxcFkA4RSSTI7keeUtFqp)
[![demo](https://asciinema.org/a/E06efxcFkA4RSSTI7keeUtFqp.svg)](https://asciinema.org/a/E06efxcFkA4RSSTI7keeUtFqp)

🧰 Use Cases
---
Expand All @@ -28,180 +28,206 @@ mult -- command --you=want --to=run
💾 Installation
---

**homebrew**:
### Pre-built binaries

```sh
brew install dhth/tap/mult
```
Download a pre-built binary from the [latest
release](https://github.com/dhth/mult/releases/latest). See [Verifying release
artifacts](#-verifying-release-artifacts) for instructions on verifying your
download.

### Install from source

**go**:
You can also install from source using the `go` toolchain:

```sh
go install github.com/dhth/mult@latest
```

**Arch Linux**:
⚡️ Quick start
---

```sh
yay -S mult
mult -n 10 -- curl -sSif https://flaky.service.example
```

Or get a binary directly from a
[release](https://github.com/dhth/mult/releases). Read more about verifying the
authenticity of released artifacts [here](#-verifying-release-artifacts).
This runs the command 10 times, shows the status and duration of each run in the
left pane, and displays the selected run's output in the right pane. Press
`<tab>` to switch panes and `?` for help.

⚡️ Usage
---
`mult` runs commands concurrently by default. Without `--num-runs`, it starts 5
runs. Use `--sequential` to run them one at a time.

```text
Usage:
mult [flags] -- <command>

Examples:
mult -s -n 10 -d 1000 -- curl -sif -m 5 'https://some.url/that?fails=sometimes'

Flags:
-d, --delay int time (in ms) to sleep for between runs
-f, --follow start with "follow mode" ON (ie, automatically select the latest command run)
-h, --help help for mult
-i, --interactive accept flag values interactively (takes precendence over -n)
-n, --num-runs int number of times to run the command (default 5)
-s, --sequential whether to invoke the command sequentially
-F, --stop-on-first-failure whether to stop after first failure
-S, --stop-on-first-success whether to stop after first success
The `--` separator prevents command flags from being interpreted as `mult`
flags. `mult` executes the command directly, without invoking a shell, and
captures its combined standard output and standard error. To use shell features
such as pipes, redirects, or variable expansion, invoke a shell explicitly:

```sh
mult -- sh -c 'curl -sS https://example.com | wc -c'
```

### Specify number of runs
Each invocation receives a 1-indexed `MULT_RUN_NUM` environment variable.

```bash
mult -n=10 -- yourcommand
⚡️ Common workflows
---

### Choose the number of runs

```sh
mult -n 10 -- yourcommand
```

### Ask for number of runs
The number of runs must be between 2 and 1000, inclusive. To enter it
interactively instead, use:

```bash
```sh
mult -i -- yourcommand
```

### Run sequentially

By default, `mult` executes all runs concurrently. Use `-s` for sequentially
execution.

```bash
```sh
mult -s -- yourcommand
```

[![sequential-runs](https://asciinema.org/a/1AeDGwgIF7bJ7QcV73Zhgqkb3.svg)](https://asciinema.org/a/1AeDGwgIF7bJ7QcV73Zhgqkb3)

### Add delay (in milliseconds) between runs
### Pause between runs

```bash
mult -s -d=500 -- yourcommand
Use `--delay` to wait between sequential runs. The value is in milliseconds.

```sh
mult -s -d 500 -- yourcommand
```

[![add-delay-between-runs](https://asciinema.org/a/vdGk7tf5sXYFZyb77PmN7ciaG.svg)](https://asciinema.org/a/vdGk7tf5sXYFZyb77PmN7ciaG)

### Stop at first failure
### Stop on the first failure or success

```bash
mult -s -F -- yourcommand
```sh
mult -s -F -- yourcommand # stop on the first failure
mult -s -S -- yourcommand # stop on the first success
```

### Stop at first success
[![stop-on-the-first-failure-or-success](https://asciinema.org/a/IeasGG4AVDLlLfTxKqETjskDD.svg)](https://asciinema.org/a/IeasGG4AVDLlLfTxKqETjskDD)

```bash
mult -s -S -- yourcommand
### Follow the latest run

In sequential mode, follow mode automatically selects the latest run:

```sh
mult -s -f -- yourcommand
```

[![stop-on-first-failure-success](https://asciinema.org/a/IeasGG4AVDLlLfTxKqETjskDD.svg)](https://asciinema.org/a/IeasGG4AVDLlLfTxKqETjskDD)
Press `<ctrl+f>` to toggle follow mode from the run list or output pane.

*Note: `-d`, `-F`, `-S` only apply in sequential run mode.*
The `--delay`, `--follow`, `--stop-on-first-failure`, and
`--stop-on-first-success` flags only apply in sequential mode.

📟 TUI
---

![TUI](https://tools.dhruvs.space/images/mult/v0-3-0/tui.png)

`mult`'s TUI has 3 views:
- Command Run List View
- Output View
- Help View

### Keyboard Shortcuts

**General**

| Key | Action |
|-------------|----------------------------|
| `tab` | Switch focus between panes |
| `?` | Show help view |
| `q` / `Esc` | Go back or quit |
| `ctrl+c` | Quit immediately |

**Command Run List View**

| Key | Action |
|-----------|-------------------------------------|
| `j` / `↓` | Go to next run |
| `k` / `↑` | Go to previous run |
| `l` / `→` | Go to next page (if applicable) |
| `h` / `←` | Go to previous page (if applicable) |
| `g` | Go to start of the list |
| `G` | Go to the end of the list |
| `ctrl+r` | Restart all runs |
| `ctrl+f` | Toggle follow mode |

**Output View**

| Key | Action |
|-----------|--------------------|
| `j` / `↓` | Scroll output down |
| `k` / `↑` | Scroll output up |
| `l` / `→` | Go to next run |
| `h` / `←` | Go to previous run |
| `ctrl+r` | Restart all runs |
| `ctrl+f` | Toggle follow mode |
`mult` has three views:

- **Command Run List View** — Shows the status and duration of each run
- **Output View** — Shows the combined output of the selected run
- **Help View** — Shows the available keyboard shortcuts

### Keyboard shortcuts

#### General

| Key | Action |
|-------------------------|----------------------------|
| `<tab>` / `<shift+tab>` | Switch focus between panes |
| `?` | Show or hide the help view |
| `q` / `<esc>` | Go back or quit |
| `<ctrl+c>` | Quit immediately |

#### Command Run List View

| Key | Action |
|------------|-------------------------------------|
| `j` / `↓` | Go to next run |
| `k` / `↑` | Go to previous run |
| `l` / `→` | Go to next page (if applicable) |
| `h` / `←` | Go to previous page (if applicable) |
| `g` | Go to start of the list |
| `G` | Go to the end of the list |
| `<ctrl+r>` | Restart all runs |
| `<ctrl+f>` | Toggle follow mode |

#### Output View

| Key | Action |
|------------|--------------------|
| `j` / `↓` | Scroll output down |
| `k` / `↑` | Scroll output up |
| `l` / `→` | Go to next run |
| `h` / `←` | Go to previous run |
| `<ctrl+r>` | Restart all runs |
| `<ctrl+f>` | Toggle follow mode |

`>_` CLI reference
---

```text
mult [flags] -- <command>
```

| Flag | What it does |
|---------------------------------|-----------------------------------------------------------|
| `-n`, `--num-runs <number>` | Set the number of runs (default: 5) |
| `-i`, `--interactive` | Prompt for the number of runs; takes precedence over `-n` |
| `-s`, `--sequential` | Run commands sequentially |
| `-d`, `--delay <milliseconds>` | Wait between sequential runs |
| `-f`, `--follow` | Start sequential runs with follow mode enabled |
| `-F`, `--stop-on-first-failure` | Stop sequential execution after the first failure |
| `-S`, `--stop-on-first-success` | Stop sequential execution after the first success |
| `-h`, `--help` | Show help |

Run `mult --help` for the authoritative command-line reference.

🔐 Verifying release artifacts
---

In case you get the `mult` binary directly from a
[release](https://github.com/dhth/mult/releases), you may want to verify its
authenticity. Checksums are applied to all released artifacts, and the resulting
checksum file is signed using
[cosign](https://docs.sigstore.dev/cosign/installation/).
Each release includes checksums for all artifacts. The checksum file is signed
using [cosign](https://docs.sigstore.dev/cosign/installation/) (version
`3.1.3`).

Steps to verify (replace `A.B.C` in the commands listed below with the version
you want):
Replace `x.y.z` below with the release version you want to verify.

1. Download the following files from the release:
1. Get the checksum and cosign signature from the release:

- mult_A.B.C_checksums.txt
- mult_A.B.C_checksums.txt.pem
- mult_A.B.C_checksums.txt.sig
```shell
curl -sSLO https://github.com/dhth/mult/releases/download/vx.y.z/mult_x.y.z_checksums.txt
curl -sSLO https://github.com/dhth/mult/releases/download/vx.y.z/mult_x.y.z_checksums.txt.sigstore.json
```

2. Verify the signature:
2. Verify the checksum file's signature:

```shell
cosign verify-blob mult_A.B.C_checksums.txt \
--certificate mult_A.B.C_checksums.txt.pem \
--signature mult_A.B.C_checksums.txt.sig \
--certificate-identity-regexp 'https://github\.com/dhth/mult/\.github/workflows/.+' \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com"
```
```shell
cosign verify-blob \
--bundle mult_x.y.z_checksums.txt.sigstore.json \
--certificate-identity-regexp 'https://github\.com/dhth/mult/\.github/workflows/.+' \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
mult_x.y.z_checksums.txt
```

3. Download the compressed archive you want, and validate its checksum:
3. Download the archive for your platform and validate its checksum. For example,
for Linux x86-64:

```shell
curl -sSLO https://github.com/dhth/mult/releases/download/vA.B.C/mult_A.B.C_linux_amd64.tar.gz
sha256sum --ignore-missing -c mult_A.B.C_checksums.txt
```
```shell
curl -sSLO https://github.com/dhth/mult/releases/download/vx.y.z/mult_x.y.z_linux_amd64.tar.gz
sha256sum --ignore-missing -c mult_x.y.z_checksums.txt
```

3. If checksum validation goes through, uncompress the archive:
4. Once both checks pass, extract the archive:

```shell
tar -xzf mult_A.B.C_linux_amd64.tar.gz
./mult -h
# profit!
```
```shell
tar -xzf mult_x.y.z_linux_amd64.tar.gz
./mult -h
# profit!
```
4 changes: 2 additions & 2 deletions internal/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ func NewRootCommand() *cobra.Command {

rootCmd := &cobra.Command{
Use: "mult [flags] -- <command>",
Short: "Run a command multiple times and glance the outputs",
Example: `mult -s -n 10 -d 1000 -- curl -sif -m 5 'https://some.url/that?fails=sometimes'`,
Short: "Run a command multiple times and glance at the outputs",
Example: `mult -s -n 10 -d 1000 -- curl -sSif 'https://flaky.service.example'`,
SilenceUsage: true,
Args: cobra.MinimumNArgs(1),
PreRunE: func(_ *cobra.Command, _ []string) error {
Expand Down