[![pipeline status](https://gitlab.com/krakrjak/restman/badges/master/pipeline.svg)](https://gitlab.com/krakrjak/restman/-/commits/master) [![Latest Release](https://gitlab.com/krakrjak/restman/-/badges/release.svg)](https://gitlab.com/krakrjak/restman/-/releases) [![License: BSD2](https://img.shields.io/badge/license-BSD2-blue.svg)](LICENSE) # RESTMan **RESTMan** is a keyboard-driven TUI HTTP client for the terminal. Compose and send HTTP(S) requests interactively — or script one-shot requests from the command line — without leaving your shell. [[_TOC_]] --- ## Features - **All HTTP methods** — GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, CONNECT, PATCH, or any custom verb - **Interactive TUI** — full-screen [Brick](https://hackage.haskell.org/package/brick)-based terminal UI - **Oneshot mode** — `--oneshot URI` prints the response body to stdout and exits (scriptable like `curl`) - **Quick Request mode** — pass a URI on the command line to pre-populate the TUI and fire the request immediately - **Syntax-highlighted responses** — MIME-type-aware, pygments colour theme via [Skylighting](https://hackage.haskell.org/package/skylighting) - **Bi-directional scroll** — arrow keys, Vi/Vim keys (`hjkl`), Page Up/Down, and mouse wheel - **Custom headers** — add, edit, enable, or disable individual headers in a table with horizontal scroll - **Default headers toggle** — view and toggle the wreq default headers (`Accept-Encoding`, `User-Agent`, …) - **Request payloads** — provide a body inline (`--payload`) or from a file (`--payload-file`) - **TLS / HTTPS** — via `http-client-tls`; 30-second timeout guards against captive portals and slow proxies - **Shell completions** — HTTP method names auto-complete via `optparse-applicative` --- ## TUI Preview ``` Header(s) Payload Session Help ================================================================================ ╭── Verb ──╮ ╭─────────────────── URL to Query? ──────────────────────────╮ │ GET ▼│ │ https://api.example.com/v1/items │ ╰──────────╯ ╰────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────╮ │ [X] Use Default Headers │ │ ┌──────────────────────────────────────────────────────────────────┐ │ │ │ Accept-Encoding: gzip, deflate, br │ │ │ │ User-Agent: Haskell wreq-0.5.4.1/0.0.0.0 │ │ │ └──────────────────────────────────────────────────────────────────┘ │ ╰──────────────────────────────────────────────────────────────────────────╯ ╭─ Active ──╮╭────────────── Name ─────────────╮╭──────── Value ───────────╮ │ [X] ││ Authorization ││ Bearer my-token │ │ [ ] ││ X-Request-ID ││ deadbeef-cafe │ ╰───────────╯╰─────────────────────────────────╯╰───────────────────────────╯ [+ Add Custom Header] ╭────────────────────────── Response Body ──────────────────────────────╮▲ │ { │ │ │ "items": [ │▓│ │ { "id": 1, "name": "widget" } │ │ │ ] │▼│ ╰───────────────────────────────────────────────────────────────────────╯◄▓► ``` --- ## Platform Support | Platform | Status | |---|---| | Linux | ✓ Fully supported | | macOS — Intel | ✓ Supported | | macOS — Apple Silicon (M1 / M2 / M3) | ✓ Supported (requires `llvm`, see below) | | Windows via WSL2 + Debian | ✓ Supported — recommended Windows path | | Windows native | ⚠ Experimental — supported since [Brick 2.0](https://hackage.haskell.org/package/brick) / [vty 6.0](https://hackage.haskell.org/package/vty) via [`vty-windows`](https://hackage.haskell.org/package/vty-windows), but builds have been unreliable; see [Known Limitations](#known-limitations) | --- ## Prerequisites | Tool | Version | Notes | |---|---|---| | [GHC](https://www.haskell.org/ghc/) | 9.10.x | Managed automatically by Stack | | [Stack](https://docs.haskellstack.org/) | any recent | Primary build tool | | [llvm](https://llvm.org/) | — | **macOS Apple Silicon only** — `brew install llvm` | Stack will download and configure the correct GHC version (via Stackage **LTS 24.38**) on first run. --- ## Quick Start ```sh # 1. Clone the repository git clone https://gitlab.com/krakrjak/restman.git cd restman # 2. Set up the compiler (once — downloads GHC 9.10.x if needed) stack setup # 3. Build RESTMan and all dependencies stack build # 4. Launch the TUI stack exec restman ``` > **First build** resolves and compiles all Haskell dependencies, which can > take several minutes. --- ## Installation To install the `restman` binary to `~/.local/bin`: ```sh stack install ``` Add `~/.local/bin` to your `PATH` so you can run `restman` directly: ```sh # Add to ~/.bashrc, ~/.zshrc, etc. export PATH="$HOME/.local/bin:$PATH" ``` RESTMan is also listed on [Hackage](https://hackage.haskell.org/package/restman) as a package reference. --- ## Usage ### Modes of Operation | Mode | How to invoke | Description | |---|---|---| | **Interactive TUI** | `restman` | Full-screen TUI — compose and send requests interactively | | **Quick Request** | `restman URI` | TUI launches with URI pre-filled; request fires immediately | | **Oneshot** | `restman --oneshot URI` | Prints response body to stdout and exits — no TUI | ### Command-line Reference ``` Usage: restman [--help] [-m|--method METHOD] [--no-default-headers | --default-headers] [-h|--custom-header NAME_VALUE] [--payload PAYLOAD | --payload-file PATHNAME] [--oneshot] [URI] ``` | Flag | Short | Default | Description | |---|---|---|---| | `--help` | | | Show help and exit | | `--method METHOD` | `-m` | `GET` | HTTP method / verb | | `--no-default-headers` | | off | Send **only** custom headers (suppress wreq defaults) | | `--default-headers` | | on | Append custom headers after wreq defaults | | `--custom-header NAME_VALUE` | `-h` | — | Add a `Name:Value` header (repeatable) | | `--payload PAYLOAD` | | — | Inline request body (UTF-8) | | `--payload-file PATHNAME` | | — | Load request body from a file | | `URI` | | — | Target URL (positional, optional) | | `--oneshot` | | off | Non-interactive one-shot mode (requires `URI`) | `--custom-header` format: `Name:Value` — the first colon separates the header name from its value. The flag may be repeated to supply multiple headers. ### Examples ```sh # Open the TUI (no arguments) restman # Open the TUI pre-loaded with a URL (request fires immediately) restman https://httpbin.org/get # One-shot GET — print the response body to stdout restman --oneshot https://httpbin.org/get # One-shot POST with a JSON payload restman --oneshot -m POST \ -h 'Content-Type:application/json' \ --payload '{"key":"value"}' \ https://httpbin.org/post # PUT with a binary payload file, suppressing default headers restman -m PUT \ --no-default-headers \ -h 'Content-Type:application/octet-stream' \ --payload-file ./data.bin \ https://example.com/upload ``` --- ## TUI Keybindings ### Global | Key | Action | |---|---| | `Esc` / `Ctrl+Q` | Quit RESTMan | | `Tab` | Move focus to the next widget | | `Shift+Tab` | Move focus to the previous widget | | `Enter` (URL editor focused) | Send the HTTP request | ### Method Selector Popup | Key | Action | |---|---| | `↓` (on Method field) | Open the method selector popup | | `↑` / `↓` | Navigate the method list | | `Enter` | Confirm selection and close the popup | | `Esc` | Close the popup without changing the method | ### Custom Headers | Key | Action | |---|---| | `Space` on `[+ Add Custom Header]` | Add a new empty header row | | `Space` on a header checkbox | Enable / disable that header | | `Tab` into a Name or Value cell | Begin editing the cell | | `←` / `→` | Scroll cell content horizontally | ### Default Headers Toggle | Key | Action | |---|---| | `Tab` to `[X] Use Default Headers` | Move focus to the toggle | | `Space` | Toggle default headers on / off | ### Response Viewport | Key | Action | |---|---| | `↓` / `j` | Scroll down one line | | `↑` / `k` | Scroll up one line | | `→` / `l` | Scroll right one column | | `←` / `h` | Scroll left one column | | `Page Down` | Scroll down one page | | `Page Up` | Scroll up one page | | Mouse scroll | Scroll three lines up / down | --- ## Building from Source ### Linux / Debian (and Windows WSL2) Install the required system packages: ```sh apt install haskell-stack libtinfo-dev libz1g-dev ``` Upgrade to the latest Stack release, then set up the compiler and build: ```sh stack upgrade # may prompt for sudo stack setup # downloads GHC 9.10.x stack build ``` ### macOS — Intel ```sh stack setup stack build ``` ### macOS — Apple Silicon (M1 / M2 / M3) Install [ghcup](https://www.haskell.org/ghcup/) and LLVM: ```sh brew install llvm ``` Export the LLVM paths (add these to your shell profile): ```sh export PATH="/opt/homebrew/opt/llvm/bin:$PATH" export LDFLAGS="-L/opt/homebrew/opt/llvm/lib" export CPPFLAGS="-I/opt/homebrew/opt/llvm/include" ``` Then build with the `aarch64` target: ```sh stack --arch aarch64 --system-ghc build ``` ### Windows **WSL2 (recommended):** Use WSL2 with a Debian distribution and follow the Linux / Debian steps above. **Windows native (experimental):** Native Windows support was introduced in vty 6.0 / Brick 2.0 via [`vty-windows`](https://hackage.haskell.org/package/vty-windows), which RESTMan picks up transitively through Brick. However, `vty-windows` has a tightly pinned `Win32` dependency and build failures have been reported. If you want to attempt a native build, install [GHCup](https://www.haskell.org/ghcup/) for Windows, then: ```sh stack setup stack build ``` Please report any success or failure in the [issue tracker](https://gitlab.com/krakrjak/restman/-/issues). --- ## Running Tests ```sh stack test ``` The test suite uses [Tasty](https://hackage.haskell.org/package/tasty) with HUnit unit tests, Hedgehog property tests, and an in-process HTTP echo server for integration tests. --- ## Known Limitations - **Windows native (experimental)** — native Windows support was added in [vty 6.0](https://hackage.haskell.org/package/vty) and [Brick 2.0](https://hackage.haskell.org/package/brick) via the [`vty-windows`](https://hackage.haskell.org/package/vty-windows) backend. However, `vty-windows` has a tightly pinned `Win32` dependency and has had reported build failures. WSL2 + Debian remains the most reliable Windows path until `vty-windows` stabilises. - **8 KB header line limit** — `http-client` enforces a hard 8 KB limit per response header line. Sites with very large headers (e.g. `slashdot.org`'s `Content-Security-Policy`) will produce an `OverlongHeaders` exception. - **Captive portals** — the 30-second response timeout guards against infinite hangs, but portal authentication pages are not navigable from the TUI. --- ## Contributing Bug reports and feature requests: [gitlab.com/krakrjak/restman/issues](https://gitlab.com/krakrjak/restman/-/issues) Before submitting a merge request, please ensure: - `stack build` passes with no warnings - `stack test` is green - Code passes `hlint` and is formatted with `stylish-haskell` - [pre-commit](https://pre-commit.com/) hooks are installed (`pre-commit install`) --- ## License [BSD 2-Clause](LICENSE) — © Zac Slade, Boyd Stephen Smith Jr.