diff --git a/readme.md b/readme.md
index 4af0b0c..6671b73 100644
--- a/readme.md
+++ b/readme.md
@@ -41,6 +41,14 @@ Run a bot with some custom listeners
 cargo run --bin simple_bot_listener
 ```
 
+## Bot with Example Custom Command
+Run a bot with some custom listeners
+
+```
+cargo run --bin bot_cmd_example
+```
+
+
 
 
 # Example Code
@@ -50,7 +58,7 @@ cargo run --bin simple_bot_listener
 Uses Env defined variables to create and run the bot
 
 ```rust
-use forcebot_rs_v2::botcore::bot::Bot;
+use forcebot_rs_v2::Bot;
 
 #[tokio::main]
 pub async fn main() {
@@ -73,8 +81,9 @@ Example listener listens for a moderator badge and reply in chat
 ```rust
 use std::sync::Arc;
 
-use forcebot_rs_v2::botcore::{bot::Bot, bot_objects::listener::asyncfn_box};
-use forcebot_rs_v2::botcore::bot_objects::listener::Listener;
+use forcebot_rs_v2::Bot;
+use forcebot_rs_v2::asyncfn_box;
+use forcebot_rs_v2::Listener;
 use twitch_irc::message::ServerMessage;
 
 
@@ -124,6 +133,49 @@ pub async fn main() {
 
 ```
 
+## Bot with Custom command
+
+```rust
+use std::sync::Arc;
+
+use forcebot_rs_v2::Bot;
+use forcebot_rs_v2::asyncfn_box;
+use forcebot_rs_v2::Command;
+use twitch_irc::message::ServerMessage;
+
+
+#[tokio::main]
+pub async fn main() {
+
+    /* Create the bot using env */
+    let mut bot = Bot::new();
+
+    /* 1. Create a new blank cmd */
+    let mut cmd = Command::new("tester".to_string(),"".to_string());
+
+    /* 2. Define an async fn callback execution */
+    async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> {
+        if let ServerMessage::Privmsg(msg) = message {
+            match bot.client.say_in_reply_to(&msg, String::from("cmd tested")).await {
+                Ok(_) => return Result::Ok("Success".to_string()) ,
+                Err(_) => return Result::Err("Not Valid message type".to_string()) 
+            }
+        }
+        Result::Err("Not Valid message type".to_string()) 
+    }
+
+    /* 3. Set and Store the execution body using `async_box()`  */
+    cmd.set_exec_fn(asyncfn_box(execbody));
+
+    /* 4. Load the cmd into the bot */
+    bot.load_command(cmd);
+
+    /* Run the bot */
+    bot.run().await;
+
+}
+```
+
 # Crate Rust Documentation 
 
 Create Crate documentation
diff --git a/src/bin/bot_cmd_example.rs b/src/bin/bot_cmd_example.rs
new file mode 100644
index 0000000..fa3d6a6
--- /dev/null
+++ b/src/bin/bot_cmd_example.rs
@@ -0,0 +1,51 @@
+//! Bot with custom example commands that responds to caller if allowed
+//! 
+//! Be sure the followig is defined in `.env` 
+//! - login_name
+//! - access_token
+//! - bot_channels
+//! - prefix
+//! - bot_admins
+//! 
+//! Bot access tokens be generated here - 
+//! - Get a Bot Chat Token here - <https://twitchtokengenerator.com>
+//! - More Info - <https://dev.twitch.tv/docs/authentication>
+
+use std::sync::Arc;
+
+use forcebot_rs_v2::Bot;
+use forcebot_rs_v2::asyncfn_box;
+use forcebot_rs_v2::Command;
+use twitch_irc::message::ServerMessage;
+
+
+#[tokio::main]
+pub async fn main() {
+
+    /* Create the bot using env */
+    let mut bot = Bot::new();
+
+    /* 1. Create a new blank cmd */
+    let mut cmd = Command::new("tester".to_string(),"".to_string());
+
+    /* 2. Define an async fn callback execution */
+    async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> {
+        if let ServerMessage::Privmsg(msg) = message {
+            match bot.client.say_in_reply_to(&msg, String::from("cmd tested")).await {
+                Ok(_) => return Result::Ok("Success".to_string()) ,
+                Err(_) => return Result::Err("Not Valid message type".to_string()) 
+            }
+        }
+        Result::Err("Not Valid message type".to_string()) 
+    }
+
+    /* 3. Set and Store the execution body using `async_box()`  */
+    cmd.set_exec_fn(asyncfn_box(execbody));
+
+    /* 4. Load the cmd into the bot */
+    bot.load_command(cmd);
+
+    /* Run the bot */
+    bot.run().await;
+
+}
diff --git a/src/bin/simple_bot.rs b/src/bin/simple_bot.rs
index 674a5d9..cbe682b 100644
--- a/src/bin/simple_bot.rs
+++ b/src/bin/simple_bot.rs
@@ -10,7 +10,7 @@
 //! - Get a Bot Chat Token here - <https://twitchtokengenerator.com>
 //! - More Info - <https://dev.twitch.tv/docs/authentication>
 
-use forcebot_rs_v2::botcore::bot::Bot;
+use forcebot_rs_v2::Bot;
 
 #[tokio::main]
 pub async fn main() {
diff --git a/src/bin/simple_bot_listener.rs b/src/bin/simple_bot_listener.rs
index b2a9141..11a62e9 100644
--- a/src/bin/simple_bot_listener.rs
+++ b/src/bin/simple_bot_listener.rs
@@ -13,8 +13,9 @@
 
 use std::sync::Arc;
 
-use forcebot_rs_v2::botcore::{bot::Bot, bot_objects::listener::asyncfn_box};
-use forcebot_rs_v2::botcore::bot_objects::listener::Listener;
+use forcebot_rs_v2::Bot;
+use forcebot_rs_v2::asyncfn_box;
+use forcebot_rs_v2::Listener;
 use twitch_irc::message::ServerMessage;
 
 
diff --git a/src/botcore/bot.rs b/src/botcore/bot.rs
index e21de83..4292dac 100644
--- a/src/botcore/bot.rs
+++ b/src/botcore/bot.rs
@@ -5,6 +5,8 @@ use twitch_irc::{login::StaticLoginCredentials, message::ServerMessage, SecureTC
 use dotenv::dotenv;
 use std::{env, sync::Arc};
 
+use crate::Command;
+
 use super::bot_objects::listener::Listener;
 
 
@@ -13,15 +15,19 @@ use super::bot_objects::listener::Listener;
 pub struct Bot 
 {
     /// Prefix for commands
-    _prefix: char,
+    prefix: String,
     /// inbound chat msg stream
     incoming_msgs: Mutex<UnboundedReceiver<ServerMessage>>,
     /// outbound chat client msg stream
     pub client: TwitchIRCClient<SecureTCPTransport,StaticLoginCredentials>,
     /// joined channels
     botchannels: Vec<String>,
+    /// admin chatters
+    admins : Vec<String>,
     /// listeners
     listeners: Vec<Listener>,
+    /// commands
+    commands: Vec<Command>,
 }
 
 
@@ -42,16 +48,17 @@ impl Bot
         let oauth_token = env::var("access_token").unwrap().to_owned();
         let prefix = env::var("prefix")
             .unwrap()
-            .to_owned()
-            .chars()
-            .next()
-            .expect("ERROR : when defining prefix");
+            .to_owned();
+            // .chars()
+            // .next()
+            // .expect("ERROR : when defining prefix");
 
         let mut botchannels = Vec::new();
 
         for chnl in env::var("bot_channels").unwrap().split(',') {
                 botchannels.push(chnl.to_owned());
         }
+    
 
         Bot::new_from(bot_login_name, oauth_token, prefix, botchannels)
        
@@ -61,7 +68,7 @@ impl Bot
     /// Creates a new `Bot` using bot information
     /// 
     /// Bot joined channels will include channels from `.env` and `botchannels` argument
-    pub fn new_from(bot_login_name:String,oauth_token:String,prefix:char,botchannels:Vec<String>) -> Bot {
+    pub fn new_from(bot_login_name:String,oauth_token:String,prefix:String,botchannels:Vec<String>) -> Bot {
 
         dotenv().ok();
         let bot_login_name = bot_login_name;
@@ -81,12 +88,24 @@ impl Bot
             botchannels_all.push(chnl.to_owned());
         }
 
+
+        let mut admins = Vec::new();
+
+        if let Ok(value) = env::var("bot_admins") {
+            for admin in value.split(',') {
+                admins.push(String::from(admin))        
+            }
+        }
+
+
         Bot {
-             _prefix : prefix,
+             prefix,
              incoming_msgs : Mutex::new(incoming_messages),
              client,
              botchannels : botchannels_all,
              listeners : vec![],
+             commands : vec![],
+             admins,
         }
     }
 
@@ -115,6 +134,14 @@ impl Bot
                         let _ = listener.execute_fn(bot.clone(),message.clone()).await;
                     }
                 }
+                for cmd in &(*bot).commands {
+                    
+                    let a = cmd.clone();
+                    if a.command_triggered(bot.clone(),message.clone()) {
+                        
+                        let _ = cmd.execute_fn(bot.clone(),message.clone()).await;
+                    }
+                }
             }
             drop(in_msgs_lock);
         });
@@ -126,5 +153,20 @@ impl Bot
     pub fn load_listener(&mut self,l : Listener) {
         self.listeners.push(l);
     }
+
+    /// Loads a `Command` into the bot
+    pub fn load_command(&mut self,c : Command) {
+        self.commands.push(c);
+    }
+    
+
+
+    pub fn get_prefix(&self) -> String {
+        self.prefix.clone()
+    }
+
+    pub fn get_admins(&self) -> Vec<String> {
+        self.admins.clone()
+    }
     
 }
\ No newline at end of file
diff --git a/src/botcore/bot_objects.rs b/src/botcore/bot_objects.rs
index baefb0b..7bb8186 100644
--- a/src/botcore/bot_objects.rs
+++ b/src/botcore/bot_objects.rs
@@ -1 +1,23 @@
-pub mod listener;
\ No newline at end of file
+pub mod listener;
+pub mod command;
+
+
+use std::boxed::Box;
+use std::future::Future;
+use std::pin::Pin;
+use std::sync::Arc;
+
+use twitch_irc::message::ServerMessage;
+
+use super::bot::Bot;
+
+pub type ExecBody = Box<
+    dyn Fn(Arc<Bot>,ServerMessage) -> Pin<Box<dyn Future<Output = Result<String,String>> + Send>> + Send + Sync,
+>;
+
+pub fn asyncfn_box<T>(f: fn(Arc<Bot>,ServerMessage) -> T) -> ExecBody
+where
+    T: Future<Output = Result<String,String>> + Send + 'static,
+{
+    Box::new(move |a,b| Box::pin(f(a,b)))
+}
\ No newline at end of file
diff --git a/src/botcore/bot_objects/command.rs b/src/botcore/bot_objects/command.rs
new file mode 100644
index 0000000..2239991
--- /dev/null
+++ b/src/botcore/bot_objects/command.rs
@@ -0,0 +1,157 @@
+use std::sync::Arc;
+
+use twitch_irc::message::ServerMessage;
+
+use crate::{asyncfn_box, botcore::bot::Bot};
+
+use super::ExecBody;
+
+/// Bot `Command` that stores trigger condition callback and a execution functon
+/// 
+/// A prefix character or phrase can be defined for the bot to evaluate a trigger condition
+/// 
+/// A command or command phrase defines the phrase after the prefix phrase
+/// 
+/// If no min badge role is provided, Broadcaster is defaulted. All commands require at least a vip role
+/// 
+/// AdminOnly commands can only be ran by admin 
+/// 
+/// Use `asyncfn_box()` on custom async execution bodies
+#[derive(Clone)]
+pub struct Command
+{
+    command : String,
+    exec_fn : Arc<ExecBody>,
+    min_badge : String,
+    admin_only : bool,
+    prefix : String,
+    custom_cond_fn : fn(Arc<Bot>,ServerMessage) -> bool,
+}
+
+impl Command
+{
+
+    /// Creates a new empty `Command` using command `String` and prefix `String`
+    /// Pass an empty string prefix if the bot should use the bot default
+    ///    
+    /// Call `set_trigger_cond_fn()` and `set_exec_fn()` to trigger & execution function callbacks
+    /// if a blank prefix is given, the bot will look for the bot prefix instead
+    /// 
+    /// By default, the new command is admin_only
+    pub fn new(command:String,prefix:String) -> Command {
+
+        async fn execbody(_:Arc<Bot>,_:ServerMessage) -> Result<String,String> 
+        { Result::Ok("success".to_string()) }
+
+        Command {
+            command ,
+            prefix ,
+            exec_fn : Arc::new(asyncfn_box(execbody)),
+            min_badge : "vip".to_string(),
+            admin_only : true,
+            custom_cond_fn : |_:Arc<Bot>,_:ServerMessage| true,
+        }
+    }
+
+    /// set a trigger conditin callback that returns true if the listener shoud trigger
+    pub fn set_custom_cond_fn(&mut self,cond_fn: fn(Arc<Bot>,ServerMessage) -> bool) {
+        self.custom_cond_fn = cond_fn;
+    }
+
+    /// sets the execution body of the listener for when it triggers
+    /// 
+    /// Use`asyncfn_box()` on the async fn when storing
+    /// 
+    /// 
+    pub fn set_exec_fn(&mut self,exec_fn:ExecBody ) {
+        self.exec_fn = Arc::new(exec_fn);
+    }
+
+    /// checks if the trigger condition is met
+    /// specifically if the message is a valid command and min badge roles provided
+    /// 
+    pub fn command_triggered(&self,bot:Arc<Bot>,msg:ServerMessage) -> bool {
+
+    
+        fn cmd_called(cmd:&Command,bot:Arc<Bot>,message:ServerMessage) -> bool {
+            if let ServerMessage::Privmsg(msg) = message {
+                // dbg!(msg.clone());
+                let mut prefixed_cmd = "".to_string();
+                if cmd.prefix == "" {
+                    prefixed_cmd.push_str(&bot.get_prefix());
+                } else { 
+                    prefixed_cmd.push_str(&cmd.prefix); 
+                }
+                prefixed_cmd.push_str(&cmd.command);
+                return msg.message_text.starts_with(prefixed_cmd.as_str())
+            } else { false }
+        }
+
+        
+        fn caller_badge_ok(cmd:&Command,_bot:Arc<Bot>,message:ServerMessage) -> bool {
+
+            if let ServerMessage::Privmsg(msg) = message {
+                // dbg!(msg.clone())
+                for badge in msg.badges {
+                    
+                    match cmd.min_badge.as_str() {
+                        "broadcaster" => {
+                            if badge.name == cmd.min_badge { return true }
+                            else { return false } 
+                        },
+                        "moderator" => {
+                            match badge.name.as_str() {
+                                "moderator" | "broadcaster" => return true,
+                                _ => (),
+                            }
+                        },
+                        "vip" => {
+                            match badge.name.as_str() {
+                                "vip" | "moderator" | "broadcaster" => return true,
+                                _ => (),
+                            }
+                        },
+                        _ => return false,
+                    }
+                }
+
+                return false;
+            } else {
+                return false;
+            }
+
+        }
+
+        
+        /// determines if the command caller can run the command 
+        /// based on admin_only flag
+        /// 
+        /// callers who are admins can run admin_only commands
+        /// callers can run non-admin_only commands
+        fn admin_only_ok(cmd:&Command,bot:Arc<Bot>,message:ServerMessage) -> bool {
+
+            if let ServerMessage::Privmsg(msg) = message {
+                if cmd.admin_only && bot.get_admins().contains(&msg.sender.login) {
+                    return true;
+                } else {
+                    return false;
+                }
+            } else { false }
+        }
+
+        fn custom_cond_ok(cmd:&Command,bot:Arc<Bot>,message:ServerMessage) -> bool {
+            (cmd.custom_cond_fn)(bot,message)
+        }
+
+        cmd_called(self, bot.clone(), msg.clone()) && 
+        caller_badge_ok(self, bot.clone(), msg.clone()) &&
+        admin_only_ok(self, bot.clone(), msg.clone()) &&
+        custom_cond_ok(self, bot, msg)
+
+    }
+
+    /// executes the listeners executon body
+    pub async fn execute_fn(&self,bot:Arc<Bot>,msg:ServerMessage) -> Result<String, String> {
+        (self.exec_fn)(bot,msg).await
+    }
+}
diff --git a/src/botcore/bot_objects/listener.rs b/src/botcore/bot_objects/listener.rs
index 0f953c4..2bebb8e 100644
--- a/src/botcore/bot_objects/listener.rs
+++ b/src/botcore/bot_objects/listener.rs
@@ -2,7 +2,9 @@ use std::sync::Arc;
 
 use twitch_irc::message::ServerMessage;
 
-use crate::botcore::bot::Bot;
+use crate::{asyncfn_box, Bot};
+
+use super::ExecBody;
 
 /// Bot `Listener`` that stores trigger condition callback and a execution functon
 /// 
@@ -10,7 +12,9 @@ use crate::botcore::bot::Bot;
 #[derive(Clone)]
 pub struct Listener
 {
+    /// trigger condition
     trigger_cond_fn : fn(Arc<Bot>,ServerMessage) -> bool,
+    /// execution body
     exec_fn : Arc<ExecBody>,
 }
 
@@ -66,18 +70,3 @@ impl Listener
     }
 }
 
-
-use std::boxed::Box;
-use std::future::Future;
-use std::pin::Pin;
-
-pub type ExecBody = Box<
-    dyn Fn(Arc<Bot>,ServerMessage) -> Pin<Box<dyn Future<Output = Result<String,String>> + Send>> + Send + Sync,
->;
-
-pub fn asyncfn_box<T>(f: fn(Arc<Bot>,ServerMessage) -> T) -> ExecBody
-where
-    T: Future<Output = Result<String,String>> + Send + 'static,
-{
-    Box::new(move |a,b| Box::pin(f(a,b)))
-}
\ No newline at end of file
diff --git a/src/lib.rs b/src/lib.rs
index ce1adca..4b1a70d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,9 +1,10 @@
-//! `forcebot-rs-v2` : Twitch chat bot written in rust
+//! `forcebot-rs-v2` : Twitch chat bot written in rust.
+//! 
 //! Customize by adding additional bot objects
 //! 
 //! # Example Simple Bot
 //! ```
-//! use forcebot_rs_v2::botcore::bot::Bot;
+//! use forcebot_rs_v2::Bot;
 //!
 //! #[tokio::main]
 //!pub async fn main() {
@@ -27,8 +28,9 @@
 //! ```
 //! use std::sync::Arc;
 //! 
-//! use forcebot_rs_v2::botcore::{bot::Bot, bot_objects::listener::asyncfn_box};
-//! use forcebot_rs_v2::botcore::bot_objects::listener::Listener;
+//! use forcebot_rs_v2::Bot;
+//! use forcebot_rs_v2::asyncfn_box;
+//! use forcebot_rs_v2::Listener;
 //! use twitch_irc::message::ServerMessage;
 //! 
 //! 
@@ -77,6 +79,50 @@
 //! }
 //! ```
 //! 
+//! # Example Bot with Custom Command
+//! ```
+//! use std::sync::Arc;
 //! 
+//! use forcebot_rs_v2::Bot;
+//! use forcebot_rs_v2::asyncfn_box;
+//! use forcebot_rs_v2::Command;
+//! use twitch_irc::message::ServerMessage;
+//! 
+//! 
+//! #[tokio::main]
+//! pub async fn main() {
+//! 
+//!     /* Create the bot using env */
+//!     let mut bot = Bot::new();
+//! 
+//!     /* 1. Create a new blank cmd */
+//!     let mut cmd = Command::new("tester".to_string(),"".to_string());
+//! 
+//!     /* 2. Define an async fn callback execution */
+//!     async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> {
+//!         if let ServerMessage::Privmsg(msg) = message {
+//!             match bot.client.say_in_reply_to(&msg, String::from("cmd tested")).await {
+//!                 Ok(_) => return Result::Ok("Success".to_string()) ,
+//!                 Err(_) => return Result::Err("Not Valid message type".to_string()) 
+//!             }
+//!         }
+//!         Result::Err("Not Valid message type".to_string()) 
+//!     }
+//! 
+//!     /* 3. Set and Store the execution body using `async_box()`  */
+//!     cmd.set_exec_fn(asyncfn_box(execbody));
+//! 
+//!     /* 4. Load the cmd into the bot */
+//!     bot.load_command(cmd);
+//! 
+//!     /* Run the bot */
+//!     bot.run().await;
+//! 
+//! }
+//! ```
 
-pub mod botcore;
\ No newline at end of file
+pub mod botcore;
+pub use crate::botcore::bot::Bot;
+pub use crate::botcore::bot_objects::asyncfn_box;
+pub use crate::botcore::bot_objects::listener::Listener;
+pub use crate::botcore::bot_objects::command::Command;
\ No newline at end of file