diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 28dc3f4..0671a2e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,6 +32,11 @@ jobs: target: x86_64-unknown-linux-gnu archive: tar.gz + - name: windows + os: windows-latest + target: x86_64-pc-windows-msvc + archive: zip + steps: - name: Checkout uses: actions/checkout@v4 @@ -53,11 +58,19 @@ jobs: TARGET="${{ matrix.target }}" VERSION="${GITHUB_REF_NAME}" - cp target/$TARGET/release/$BIN_NAME . + if [[ "$TARGET" == *-windows-* ]]; then + BIN_NAME="reqsh.exe" + fi - ARCHIVE_NAME="${BIN_NAME}-${VERSION}-${TARGET}.tar.gz" + cp "target/$TARGET/release/$BIN_NAME" . - tar -czf dist/$ARCHIVE_NAME $BIN_NAME + if [[ "$TARGET" == *-windows-* ]]; then + ARCHIVE_NAME="${BIN_NAME}-${VERSION}-${TARGET}.zip" + 7z a -tzip "dist/$ARCHIVE_NAME" "$BIN_NAME" + else + ARCHIVE_NAME="${BIN_NAME}-${VERSION}-${TARGET}.tar.gz" + tar -czf "dist/$ARCHIVE_NAME" "$BIN_NAME" + fi - name: Upload Release Asset uses: softprops/action-gh-release@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 25adca4..6bce213 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.1.3 (2026-06-09) + +- Response time displayed with each request +- Windows binary support (x86_64-pc-windows-msvc) +- Absolute URLs now work (not just relative paths with `base`) + ## 0.1.2 (2026-06-06) - Variable interpolation with `{{name}}` syntax in paths, headers, and body diff --git a/Cargo.lock b/Cargo.lock index e65849b..c16564a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -907,7 +907,7 @@ dependencies = [ [[package]] name = "reqsh" -version = "0.1.2" +version = "0.1.3" dependencies = [ "colored", "dirs", diff --git a/Cargo.toml b/Cargo.toml index d2e0b03..fa6f81a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reqsh" -version = "0.1.2" +version = "0.1.3" edition = "2024" license = "MIT" authors = ["Harshil Gupta"] diff --git a/README.md b/README.md index e841bb5..1b3865b 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ Interactive HTTP shell for API workflows. Send HTTP requests, manage headers and - Variable interpolation with `{{name}}` syntax in paths, headers, and body - Query parameter support with `param: key=value` lines - Save and run requests in-session +- Response time displayed per request +- Full absolute URL support (base URL not required) - JSON response pretty-printing - Command history and rerun by index - Colored terminal output @@ -32,7 +34,7 @@ curl -fsSL https://raw.githubusercontent.com/hars-21/reqsh/main/install.sh | sh ### Pre-built binary -Download the latest binary from the [releases page](https://github.com/hars-21/reqsh/releases/latest). +Download the latest binary for your platform from the [releases page](https://github.com/hars-21/reqsh/releases/latest). macOS (Intel & Silicon), Linux (x86_64), and Windows (x86_64) are available. ### Build from source @@ -59,18 +61,24 @@ Start the REPL: reqsh ``` -Set a base URL: +Set a base URL (optional — you can use absolute URLs directly): ```bash reqsh> base https://api.example.com ``` -Send a GET request: +Send a GET request (relative path requires a base URL): ```bash reqsh> GET /users ``` +Or use an absolute URL directly: + +```bash +reqsh> GET https://jsonplaceholder.typicode.com/posts +``` + Send a POST request with headers and body: ```bash @@ -86,10 +94,10 @@ reqsh> POST /users | Command | Description | | ---------------------- | ------------------------------------ | -| `GET ` | Send GET request | -| `POST ` | Send POST request | -| `PUT ` | Send PUT request | -| `DELETE ` | Send DELETE request | +| `GET ` | Send GET request | +| `POST ` | Send POST request | +| `PUT ` | Send PUT request | +| `DELETE ` | Send DELETE request | | `base ` | Set base URL for all requests | | `header ` | Set a global header for all requests | | `set ` | Set a session variable | diff --git a/install.sh b/install.sh index 9710d7d..c60b1bc 100644 --- a/install.sh +++ b/install.sh @@ -11,12 +11,19 @@ echo "Installing $BIN_NAME..." OS="$(uname -s)" ARCH="$(uname -m)" -case "$OS" in - Linux) +case "$(echo "$OS" | tr '[:upper:]' '[:lower:]')" in + linux) OS="unknown-linux-gnu" + EXT="tar.gz" ;; - Darwin) + darwin) OS="apple-darwin" + EXT="tar.gz" + ;; + mingw*|msys*) + OS="pc-windows-msvc" + EXT="zip" + BIN_NAME="reqsh.exe" ;; *) echo "Unsupported OS: $OS" @@ -50,7 +57,7 @@ if [ -z "$VERSION" ]; then exit 1 fi -FILE="${BIN_NAME}-${VERSION}-${TARGET}.tar.gz" +FILE="${BIN_NAME}-${VERSION}-${TARGET}.${EXT}" URL="https://github.com/${REPO}/releases/download/${VERSION}/${FILE}" @@ -63,9 +70,13 @@ echo "Downloading $FILE..." curl -fsSL "$URL" -o "$ARCHIVE_PATH" echo "Extracting archive..." -tar -xzf "$ARCHIVE_PATH" -C "$TMP_DIR" +if [ "$EXT" = "zip" ]; then + unzip -o "$ARCHIVE_PATH" -d "$TMP_DIR" +else + tar -xzf "$ARCHIVE_PATH" -C "$TMP_DIR" +fi -chmod +x "$TMP_DIR/$BIN_NAME" +chmod +x "$TMP_DIR/$BIN_NAME" 2>/dev/null || true mkdir -p "$INSTALL_DIR" diff --git a/src/display.rs b/src/display.rs index 96199c7..826e6d9 100644 --- a/src/display.rs +++ b/src/display.rs @@ -1,7 +1,9 @@ +use std::time::Duration; + use colored::Colorize; use reqwest::blocking::Response; -pub fn display_response(res: Response) -> String { +pub fn display_response(res: Response, response_time: Duration) -> String { let mut output = String::new(); let status = res.status(); @@ -18,10 +20,11 @@ pub fn display_response(res: Response) -> String { }; let status_line = format!( - "{} {} {}\n", + "{} {} {} {}\n", format!("{:?}", res.version()).magenta(), - status_color.bold(), - reason_color.bold() + status_color, + reason_color, + format!("{}ms", response_time.as_millis()).bright_yellow(), ); output.push_str(&status_line); diff --git a/src/executor.rs b/src/executor.rs index 9566b38..c5e2de6 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -56,7 +56,7 @@ pub fn execute(req: Request, ctx: &ShellState) -> Result { let response = fetch(&req, base_url, global_headers); match response { - Ok(r) => Ok(display_response(r)), + Ok((res, duration)) => Ok(display_response(res, duration)), Err(e) => Err(e), } } diff --git a/src/runner.rs b/src/runner.rs index 97d10d5..0db65a9 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -1,4 +1,7 @@ -use std::collections::HashMap; +use std::{ + collections::HashMap, + time::{Duration, Instant}, +}; use crate::request::{Method, Request}; use reqwest::{ @@ -10,15 +13,18 @@ pub fn fetch( request: &Request, base_url: Option<&str>, global_headers: &HashMap, -) -> Result { +) -> Result<(Response, Duration), String> { // Client let client = Client::new(); // Url Constructor - let full_url = if (request.path.starts_with("/")) + let full_url = if request.path.starts_with("http://") || request.path.starts_with("https://") { + request.path.clone() + } else if request.path.starts_with("/") && let Some(base_url) = base_url { - format!("{base_url}{}", request.path) + let base = base_url.trim_end_matches('/'); + format!("{base}{}", request.path) } else { return Err(String::from( "Base URL not found. Use base to add base url", @@ -52,7 +58,9 @@ pub fn fetch( HeaderValue::from_bytes(value.as_bytes()).unwrap(), ); } - headers.insert(CONTENT_TYPE, "application/json".parse().unwrap()); + if !headers.contains_key(CONTENT_TYPE) { + headers.insert(CONTENT_TYPE, "application/json".parse().unwrap()); + } req_builder = req_builder.headers(headers); // Query Params @@ -65,11 +73,15 @@ pub fn fetch( req_builder = req_builder.body(body.clone()); } + // Timer + let now = Instant::now(); + // Response let result = req_builder.send(); + let response_time = now.elapsed(); match result { - Ok(response) => Ok(response), + Ok(response) => Ok((response, response_time)), Err(e) => Err(format!("{}", e)), } }