diff --git a/.github/workflows/on-pull.yml b/.github/workflows/on-pull.yml
new file mode 100644
index 0000000..0b845a0
--- /dev/null
+++ b/.github/workflows/on-pull.yml
@@ -0,0 +1,39 @@
+name: Pull request build
+
+on:
+  pull_request:
+
+jobs:
+  build-and-upload:
+    name: build and upload
+    runs-on: ${{ matrix.os }}
+
+    strategy:
+      matrix:
+        include:
+          - build: linux
+            os: ubuntu-latest
+            target: x86_64-unknown-linux-musl
+
+          - build: macos
+            os: macos-latest
+            target: aarch64-apple-darwin
+
+          - build: windows-gnu
+            os: windows-latest
+            target: x86_64-pc-windows-gnu
+
+    steps:
+      - name: Clone repository
+        uses: actions/checkout@v4
+
+      - name: Install Rust
+        # Or @nightly if you want
+        uses: dtolnay/rust-toolchain@stable
+        # Arguments to pass in
+        with:
+          # Make Rust compile to our target (defined in the matrix)
+          targets: ${{ matrix.target }}
+
+      - name: Build project
+        run: cargo build --release --target ${{ matrix.target }}
diff --git a/.github/workflows/on-release.yaml b/.github/workflows/on-release.yaml
index 531e523..aa84eda 100644
--- a/.github/workflows/on-release.yaml
+++ b/.github/workflows/on-release.yaml
@@ -29,9 +29,7 @@ jobs:
         uses: actions/checkout@v4
 
       - name: Install Rust
-        # Or @nightly if you want
         uses: dtolnay/rust-toolchain@stable
-        # Arguments to pass in
         with:
           # Make Rust compile to our target (defined in the matrix)
           targets: ${{ matrix.target }}
@@ -40,7 +38,6 @@ jobs:
         shell: bash
         run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
 
-      # TODO: Build project here
       - name: Build project
         run: cargo build --release --target ${{ matrix.target }}
 
diff --git a/Cargo.lock b/Cargo.lock
index 8c968e8..24703e0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -101,6 +101,12 @@ version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
+[[package]]
+name = "cfg_aliases"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
+
 [[package]]
 name = "clap"
 version = "4.5.4"
@@ -160,6 +166,15 @@ version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
 
+[[package]]
+name = "clipboard-win"
+version = "5.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79f4473f5144e20d9aceaf2972478f06ddf687831eafeeb434fbaf0acc4144ad"
+dependencies = [
+ "error-code",
+]
+
 [[package]]
 name = "colorchoice"
 version = "1.0.0"
@@ -255,6 +270,12 @@ version = "1.0.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
 
+[[package]]
+name = "endian-type"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
+
 [[package]]
 name = "env_filter"
 version = "0.1.0"
@@ -284,6 +305,33 @@ version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
 
+[[package]]
+name = "errno"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "error-code"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b"
+
+[[package]]
+name = "fd-lock"
+version = "4.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e5768da2206272c81ef0b5e951a41862938a6070da63bcea197899942d3b947"
+dependencies = [
+ "cfg-if",
+ "rustix",
+ "windows-sys 0.52.0",
+]
+
 [[package]]
 name = "fuzzy-matcher"
 version = "0.3.7"
@@ -331,6 +379,15 @@ version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
 
+[[package]]
+name = "home"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
 [[package]]
 name = "humantime"
 version = "2.1.0"
@@ -380,6 +437,12 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
+
 [[package]]
 name = "lock_api"
 version = "0.4.12"
@@ -423,6 +486,27 @@ dependencies = [
  "unicode-segmentation",
 ]
 
+[[package]]
+name = "nibble_vec"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
+dependencies = [
+ "smallvec",
+]
+
+[[package]]
+name = "nix"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
+dependencies = [
+ "bitflags 2.5.0",
+ "cfg-if",
+ "cfg_aliases",
+ "libc",
+]
+
 [[package]]
 name = "once_cell"
 version = "1.19.0"
@@ -482,6 +566,16 @@ dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "radix_trie"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
+dependencies = [
+ "endian-type",
+ "nibble_vec",
+]
+
 [[package]]
 name = "rand"
 version = "0.8.5"
@@ -514,7 +608,7 @@ dependencies = [
 
 [[package]]
 name = "rcon"
-version = "1.0.0"
+version = "1.0.1"
 dependencies = [
  "anyhow",
  "clap",
@@ -526,6 +620,7 @@ dependencies = [
  "inquire",
  "log",
  "rcon-client",
+ "rustyline",
  "serde",
 ]
 
@@ -590,12 +685,47 @@ version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
 
+[[package]]
+name = "rustix"
+version = "0.38.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
+dependencies = [
+ "bitflags 2.5.0",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.52.0",
+]
+
 [[package]]
 name = "rustversion"
 version = "1.0.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47"
 
+[[package]]
+name = "rustyline"
+version = "14.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7803e8936da37efd9b6d4478277f4b2b9bb5cdb37a113e8d63222e58da647e63"
+dependencies = [
+ "bitflags 2.5.0",
+ "cfg-if",
+ "clipboard-win",
+ "fd-lock",
+ "home",
+ "libc",
+ "log",
+ "memchr",
+ "nix",
+ "radix_trie",
+ "unicode-segmentation",
+ "unicode-width",
+ "utf8parse",
+ "windows-sys 0.52.0",
+]
+
 [[package]]
 name = "scopeguard"
 version = "1.2.0"
diff --git a/Cargo.toml b/Cargo.toml
index efe817a..b93db1a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "rcon"
-version = "1.0.0"
+version = "1.0.1"
 edition = "2021"
 authors = ["GDGunnars <gdg@gdg.is>", "BLAST.tv <support@blast.tv>"]
 license = "MIT OR Apache-2.0"
@@ -24,4 +24,5 @@ env_logger = "0.11.3"
 inquire = "0.7.5"
 log = "0.4.21"
 rcon-client = "0.1.2"
+rustyline = "14.0.0"
 serde = { version = "1.0.198", features = ["derive"] }
diff --git a/src/commands/action.rs b/src/commands/action.rs
index 7d6e0d8..26cdae7 100644
--- a/src/commands/action.rs
+++ b/src/commands/action.rs
@@ -1,21 +1,24 @@
 use comfy_table::{Cell, Color, Table};
 use log::error;
 use rcon_client::{AuthRequest, RCONClient, RCONConfig, RCONError, RCONRequest};
+use rustyline::error::ReadlineError;
+use rustyline::DefaultEditor;
 
 use crate::config::{self, ServerConfig};
-use inquire::Text;
 
-fn create_and_auth_client(server: ServerConfig) -> Result<RCONClient, RCONError> {
+fn create_and_auth_client(server: &ServerConfig) -> Result<RCONClient, RCONError> {
     let mut client = RCONClient::new(RCONConfig {
         url: format!("{}:{}", server.ip, server.port),
         // Optional
         read_timeout: Some(13),
         write_timeout: Some(37),
     })
-    .unwrap();
+    .expect("Could not create RCON client");
 
-    // Auth request to RCON server (SERVERDATA_AUTH)
-    client.auth(AuthRequest::new(server.pass)).unwrap();
+    // Auth request to RCON server using stored password
+    client
+        .auth(AuthRequest::new(server.pass.clone()))
+        .expect("Could not authenticate with RCON server");
 
     Ok(client)
 }
@@ -27,11 +30,9 @@ fn determine_server(use_default_server: bool) -> ServerConfig {
             error!("No default server set, please add one with `rcon server set-default`",);
             std::process::exit(2);
         }
-        println!(
-            "▲ Default server is enabled: {}",
-            cfg.default_server.as_ref().unwrap()
-        );
-        return cfg.default_server.unwrap();
+        let default_server = cfg.default_server.expect("No default server set");
+        println!("▲ Default server is enabled: {}", default_server);
+        return default_server;
     } else {
         return config::select_server_from_list();
     }
@@ -40,23 +41,52 @@ fn determine_server(use_default_server: bool) -> ServerConfig {
 pub fn shell(use_default_server: bool) -> Result<(), RCONError> {
     let server: ServerConfig = determine_server(use_default_server);
     // Create new RCON client & validate auth
-    let mut client = create_and_auth_client(server).unwrap();
+    let mut client = create_and_auth_client(&server).expect("Could not create RCON client");
+    let mut rl = DefaultEditor::new().expect("Could not create shell editor");
 
-    let mut continue_loop = true;
-    while continue_loop {
-        let command = Text::new("(q/quit) Enter RCON command:").prompt().unwrap();
-
-        if command == "q" || command == "quit" {
-            continue_loop = false;
-            continue;
+    loop {
+        println!(
+            "▲ (q/quit) Shell mode for server: {}: {}",
+            server.name, server.ip
+        );
+        let readline = rl.readline("▶︎ ");
+
+        match readline {
+            Ok(command) => {
+                if command == "q" || command == "quit" {
+                    println!("▶︎ Exiting, goodbye!");
+                    break;
+                }
+
+                if let Err(err) = rl.add_history_entry(command.as_str()) {
+                    eprintln!("Failed to add history entry: {}", err);
+                }
+
+                let mut table = Table::new();
+
+                let command_response = client.execute(RCONRequest::new(command))?;
+
+                table.add_row(vec![Cell::new(command_response.body).fg(Color::Green)]);
+                println!("▲\n{}", table);
+            }
+
+            Err(ReadlineError::Interrupted) => {
+                // CTRL-C"
+                println!("▶︎ Exiting, goodbye!");
+                break;
+            }
+
+            Err(ReadlineError::Eof) => {
+                // CTRL-D
+                println!("▶︎ Exiting, goodbye!");
+                break;
+            }
+
+            Err(err) => {
+                println!("▼ Error: {:?}", err);
+                break;
+            }
         }
-
-        let mut table = Table::new();
-
-        let command_response = client.execute(RCONRequest::new(command))?;
-
-        table.add_row(vec![Cell::new(command_response.body).fg(Color::Green)]);
-        println!("▲\n{}", table);
     }
 
     Ok(())
@@ -65,7 +95,7 @@ pub fn shell(use_default_server: bool) -> Result<(), RCONError> {
 pub fn execute_command(use_default_server: bool, command: String) -> Result<(), RCONError> {
     let server: ServerConfig = determine_server(use_default_server);
     // Create new RCON client & validate auth
-    let mut client = create_and_auth_client(server).unwrap();
+    let mut client = create_and_auth_client(&server).expect("Could not create RCON client");
 
     let mut table = Table::new();
 
diff --git a/src/commands/server.rs b/src/commands/server.rs
index 7a80848..1d86450 100644
--- a/src/commands/server.rs
+++ b/src/commands/server.rs
@@ -7,14 +7,25 @@ use inquire::{Confirm, Text};
 use log::{debug, info};
 
 pub fn add_server() {
-    let name = Text::new("Name of server:").prompt().unwrap();
-    let ip: String = Text::new("Address:").prompt().unwrap();
-    let port: String = Text::new("Port:").with_default("27015").prompt().unwrap();
-    let pass: String = Text::new("RCON password:").prompt().unwrap();
-    let set_as_default: bool = Confirm::new("Set as default server?").prompt().unwrap();
+    let name = Text::new("Name of server:")
+        .prompt()
+        .expect("Could not get server name");
+    let ip: String = Text::new("Address:")
+        .prompt()
+        .expect("Could not get server address");
+    let port: String = Text::new("Port:")
+        .with_default("27015")
+        .prompt()
+        .expect("Could not get server port");
+    let pass: String = Text::new("RCON password:")
+        .prompt()
+        .expect("Could not get RCON password");
+    let set_as_default: bool = Confirm::new("Set as default server?")
+        .prompt()
+        .expect("Could not get default server setting");
     let correct_info = Confirm::new("Is the above information correct?")
         .prompt()
-        .unwrap();
+        .expect("Could not get confirmation");
 
     if !correct_info {
         info!("Server configuration not saved");
@@ -47,26 +58,26 @@ pub fn edit_server() {
     let name = Text::new("Name of server:")
         .with_default(&server.name)
         .prompt()
-        .unwrap();
+        .expect("Could not get server name");
     let ip = Text::new("IP address:")
         .with_default(&server.ip)
         .prompt()
-        .unwrap();
+        .expect("Could not get server address");
     let port = Text::new("Port:")
         .with_default(&server.port)
         .prompt()
-        .unwrap();
+        .expect("Could not get server port");
     let pass = Text::new("RCON password:")
         .with_default(&server.pass)
         .prompt()
-        .unwrap();
+        .expect("Could not get RCON password");
 
     let override_settings = Confirm::new(&format!(
         "You want to override {} with the above info?",
         server.name
     ))
     .prompt()
-    .unwrap();
+    .expect("Could not get confirmation");
 
     if override_settings {
         let mut new_config: RconCliConfig = config::get_config();
@@ -82,7 +93,7 @@ pub fn edit_server() {
 pub fn clear_servers() {
     let confirm_prompt = Confirm::new("Are you sure you want to clear all server configurations?")
         .prompt()
-        .unwrap();
+        .expect("Could not get confirmation");
     if confirm_prompt {
         let mut cfg: RconCliConfig = config::get_config();
         cfg.clear();
@@ -101,7 +112,7 @@ pub fn remove_server() {
         server.name
     ))
     .prompt()
-    .unwrap();
+    .expect("Could not get confirmation");
     if confirm_prompt {
         let mut new_config = RconCliConfig::new(cfg.default_server, cfg.servers);
         new_config.remove_server(server.clone());
@@ -123,18 +134,20 @@ pub fn list_servers() {
     ]);
 
     let cfg: RconCliConfig = config::get_config();
-    for server in cfg.servers {
-        if cfg.default_server.is_some() && cfg.default_server.as_ref().unwrap().name == server.name
-        {
-            table.add_row(vec![
-                Cell::new(&server.name).fg(Color::Green),
-                Cell::new(&server.ip).fg(Color::Green),
-                Cell::new(&server.port).fg(Color::Green),
-                Cell::new(&server.pass).fg(Color::Green),
-                Cell::new("✓").fg(Color::Green),
-            ]);
-            continue;
+    for server in &cfg.servers {
+        if let Some(ref default) = cfg.default_server {
+            if server == default {
+                table.add_row(vec![
+                    Cell::new(&server.name).fg(Color::Green),
+                    Cell::new(&server.ip).fg(Color::Green),
+                    Cell::new(&server.port).fg(Color::Green),
+                    Cell::new(&server.pass).fg(Color::Green),
+                    Cell::new("✓").fg(Color::Green),
+                ]);
+                continue;
+            }
         }
+
         table.add_row(vec![
             Cell::new(&server.name),
             Cell::new(&server.ip),
diff --git a/src/config.rs b/src/config.rs
index ae087c6..076c2ee 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -110,19 +110,25 @@ impl ::std::default::Default for RconCliConfig {
 }
 
 pub fn get_path() -> String {
-    let path = confy::get_configuration_file_path("rcon_cli", None).unwrap();
-    return path.to_str().unwrap().to_string();
+    let path = confy::get_configuration_file_path("rcon_cli", None)
+        .expect("Could not get configuration file path");
+
+    return path
+        .to_str()
+        .expect("Could not convert path to string")
+        .to_string();
 }
 
 pub fn get_config() -> RconCliConfig {
-    let cfg: RconCliConfig = confy::load("rcon_cli", None).unwrap();
+    let cfg: RconCliConfig =
+        confy::load("rcon_cli", None).expect("Could not load configuration file");
     return cfg;
 }
 
 pub fn save_config(cfg: RconCliConfig) {
     let mut config = cfg.clone();
     config.order_servers();
-    confy::store("rcon_cli", None, config).unwrap();
+    confy::store("rcon_cli", None, config).expect("Error saving configuration file");
 }
 
 pub fn set_and_save_default_server(server: ServerConfig) {
@@ -133,11 +139,9 @@ pub fn set_and_save_default_server(server: ServerConfig) {
 
 pub fn select_server_from_list() -> ServerConfig {
     let cfg: RconCliConfig = get_config();
-    let ans = Select::new("Selected server: ", cfg.servers.clone()).prompt();
-    let selected_server = match ans {
-        Ok(choice) => choice,
-        Err(_) => panic!("There was an error, please try again"),
-    };
+    let selected_server = Select::new("Selected server: ", cfg.servers)
+        .prompt()
+        .expect("Error selecting server");
 
     return selected_server;
 }
diff --git a/src/lib.rs b/src/lib.rs
deleted file mode 100644
index e69de29..0000000
diff --git a/src/shell_tools.rs b/src/shell_tools.rs
index 0fbc8f5..aa5ce87 100644
--- a/src/shell_tools.rs
+++ b/src/shell_tools.rs
@@ -13,7 +13,7 @@ pub fn completion(path: Option<PathBuf>) -> Result<(), Error> {
         let name = cli.get_name().to_string();
 
         if let Some(location) = path {
-            generate(shell, &mut cli, name, &mut File::create(location.clone())?);
+            generate(shell, &mut cli, name, &mut File::create(&location)?);
             println!("Shell autocomplete script generated at: {:?}", location)
         } else {
             error!("▲ Could not determine location to generate shell completion script");
