diff --git a/package.json b/package.json
index 7b9b6f8..1af5bb7 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "creddy",
-  "version": "0.2.1",
+  "version": "0.2.2",
   "scripts": {
     "dev": "vite",
     "build": "vite build",
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index 809d492..ae4c32a 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -68,36 +68,6 @@ version = "1.0.71"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
 
-[[package]]
-name = "app"
-version = "0.2.1"
-dependencies = [
- "argon2",
- "auto-launch",
- "aws-config",
- "aws-sdk-sts",
- "aws-smithy-types",
- "aws-types",
- "chacha20poly1305",
- "clap",
- "dirs 5.0.1",
- "is-terminal",
- "netstat2",
- "once_cell",
- "serde",
- "serde_json",
- "sodiumoxide",
- "sqlx",
- "strum",
- "strum_macros",
- "sysinfo",
- "tauri",
- "tauri-build",
- "tauri-plugin-single-instance",
- "thiserror",
- "tokio",
-]
-
 [[package]]
 name = "argon2"
 version = "0.5.0"
@@ -975,6 +945,36 @@ dependencies = [
  "cfg-if",
 ]
 
+[[package]]
+name = "creddy"
+version = "0.2.2"
+dependencies = [
+ "argon2",
+ "auto-launch",
+ "aws-config",
+ "aws-sdk-sts",
+ "aws-smithy-types",
+ "aws-types",
+ "chacha20poly1305",
+ "clap",
+ "dirs 5.0.1",
+ "is-terminal",
+ "netstat2",
+ "once_cell",
+ "serde",
+ "serde_json",
+ "sodiumoxide",
+ "sqlx",
+ "strum",
+ "strum_macros",
+ "sysinfo",
+ "tauri",
+ "tauri-build",
+ "tauri-plugin-single-instance",
+ "thiserror",
+ "tokio",
+]
+
 [[package]]
 name = "crossbeam-channel"
 version = "0.5.8"
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index 267e53b..011a5be 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -1,14 +1,22 @@
 [package]
-name = "app"
-version = "0.2.1"
-description = "A Tauri App"
-authors = ["you"]
+name = "creddy"
+version = "0.2.2"
+description = "A friendly AWS credentials manager"
+authors = ["Joseph Montanaro"]
 license = ""
 repository = ""
-default-run = "app"
+default-run = "creddy"
 edition = "2021"
 rust-version = "1.57"
 
+[[bin]]
+name = "creddy_cli"
+path = "src/bin/creddy_cli.rs"
+
+[[bin]]
+name = "creddy"
+path = "src/main.rs"
+
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [build-dependencies]
diff --git a/src-tauri/conf/cli.wxs b/src-tauri/conf/cli.wxs
new file mode 100644
index 0000000..e3f28c3
--- /dev/null
+++ b/src-tauri/conf/cli.wxs
@@ -0,0 +1,22 @@
+
+
+    
+
+        
+            
+            
+                
+                    
+                    
+                
+            
+        
+
+        
+            
+                
+            
+        
+
+    
+
diff --git a/src-tauri/src/bin/creddy_cli.rs b/src-tauri/src/bin/creddy_cli.rs
new file mode 100644
index 0000000..02ebd4b
--- /dev/null
+++ b/src-tauri/src/bin/creddy_cli.rs
@@ -0,0 +1,45 @@
+// Windows isn't really amenable to having a single executable work as both a CLI and GUI app,
+// so we just have a second binary for CLI usage
+use creddy::{
+    cli,
+    errors::CliError,
+};
+use std::{
+    env,
+    process::{self, Command},
+};
+
+
+fn main() {
+    let args = cli::parser().get_matches();
+    if let Some(true) = args.get_one::("help") {
+        cli::parser().print_help().unwrap(); // if we can't print help we can't print an error
+        process::exit(0);
+    }
+
+    let res = match args.subcommand() {
+        None | Some(("run", _)) => launch_gui(),
+        Some(("show", m)) => cli::show(m),
+        Some(("exec", m)) => cli::exec(m),
+        _ => unreachable!(),
+    };
+
+    if let Err(e) = res {
+        eprintln!("Error: {e}");
+    }
+}
+
+
+fn launch_gui() -> Result<(), CliError>  {
+    let mut path = env::current_exe()?;
+    path.pop(); // bin dir
+    
+    // binaries are colocated in dev, but not in production
+    #[cfg(not(debug_assertions))]
+    path.pop(); // install dir
+
+    path.push("creddy.exe"); // exe in main install dir (aka gui exe)
+
+    Command::new(path).spawn()?;
+    Ok(())
+}
diff --git a/src-tauri/src/errors.rs b/src-tauri/src/errors.rs
index ae91ac5..5d6e10c 100644
--- a/src-tauri/src/errors.rs
+++ b/src-tauri/src/errors.rs
@@ -228,6 +228,8 @@ pub enum CliError {
     Request(#[from] RequestError),
     #[error(transparent)]
     Exec(#[from] ExecError),
+    #[error(transparent)]
+    Io(#[from] std::io::Error),
 }
 
 
diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
new file mode 100644
index 0000000..a594d9b
--- /dev/null
+++ b/src-tauri/src/lib.rs
@@ -0,0 +1,10 @@
+pub mod app;
+pub mod cli;
+mod config;
+mod credentials;
+pub mod errors;
+mod clientinfo;
+mod ipc;
+mod state;
+mod server;
+mod tray;
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs
index 4065849..982367e 100644
--- a/src-tauri/src/main.rs
+++ b/src-tauri/src/main.rs
@@ -3,20 +3,11 @@
     windows_subsystem = "windows"
 )]
 
-
-mod app;
-mod cli;
-mod config;
-mod credentials;
-mod errors;
-mod clientinfo;
-mod ipc;
-mod state;
-mod server;
-mod tray;
-
-
-use crate::errors::ErrorPopup;
+use creddy::{
+    app,
+    cli,
+    errors::ErrorPopup,
+};
 
 
 fn main() {
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index 42c28d6..cd61c57 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -8,7 +8,7 @@
   },
   "package": {
     "productName": "creddy",
-    "version": "0.2.1"
+    "version": "0.2.2"
   },
   "tauri": {
     "allowlist": {
@@ -44,7 +44,11 @@
       "windows": {
         "certificateThumbprint": null,
         "digestAlgorithm": "sha256",
-        "timestampUrl": ""
+        "timestampUrl": "",
+        "wix": {
+          "fragmentPaths": ["conf/cli.wxs"],
+          "componentRefs": ["CliBinary", "AddToPath"]
+        }
       }
     },
     "security": {