diff --git a/src/lib/builtins/mod.rs b/src/lib/builtins/mod.rs
index 9b40233a8e178950d4b309cf997c14b5c9135780..a81181d60a53305257abb9154dd166b74363850e 100644
--- a/src/lib/builtins/mod.rs
+++ b/src/lib/builtins/mod.rs
@@ -37,9 +37,10 @@ use std::path::Path;
 
 use parser::Terminator;
 use parser::pipelines::{PipeItem, Pipeline};
-use shell::{self, FlowLogic, Job, JobKind, Shell, ShellHistory};
 use shell::job_control::{JobControl, ProcessState};
+use shell::fork_function::fork_function;
 use shell::status::*;
+use shell::{self, FlowLogic, Job, JobKind, Shell, ShellHistory};
 use sys;
 
 const HELP_DESC: &str = "Display helpful information about a given command or list commands if \
@@ -171,10 +172,12 @@ pub fn builtin_cd(args: &[&str], shell: &mut Shell) -> i32 {
                     let pwd = shell.get_var_or_empty("PWD");
                     let pwd: &str = &pwd;
                     let current_dir = path.to_str().unwrap_or("?");
+
                     if pwd != current_dir {
                         env::set_var("OLDPWD", pwd);
                         env::set_var("PWD", current_dir);
                     }
+                    fork_function(shell, "CD_CHANGE", &["ion"]);
                 },
             );
             SUCCESS
@@ -666,4 +669,4 @@ fn builtin_isatty(args: &[&str], _: &mut Shell) -> i32 {
     }
 
     FAILURE
-}
\ No newline at end of file
+}
diff --git a/src/lib/lib.rs b/src/lib/lib.rs
index ab1ededfcf7c6693fd2b2f880ba8d07c9d15087f..045da92a627ebc97c1465de637e4336c34df6c1d 100644
--- a/src/lib/lib.rs
+++ b/src/lib/lib.rs
@@ -47,6 +47,8 @@ mod builtins;
 mod shell;
 mod ascii_helpers;
 
-pub use shell::{Binary, Capture, Fork, IonError, IonResult, Shell, ShellBuilder};
+pub use shell::binary::MAN_ION;
 pub use shell::flags;
 pub use shell::status;
+pub use shell::{Binary, Capture, Fork, IonError, IonResult, Shell, ShellBuilder};
+pub use shell::pipe_exec::job_control::JobControl;
diff --git a/src/lib/shell/binary/mod.rs b/src/lib/shell/binary/mod.rs
index 46132cb497f5be1d32ada11543b7e50ae44e5937..e7c5eaf5380c1a1abdd0d462bc15f71dd8a2d0b9 100644
--- a/src/lib/shell/binary/mod.rs
+++ b/src/lib/shell/binary/mod.rs
@@ -7,22 +7,18 @@ mod terminate;
 use self::prompt::{prompt, prompt_fn};
 use self::readln::readln;
 use self::terminate::{terminate_quotes, terminate_script_quotes};
-use super::{FlowLogic, JobControl, Shell, ShellHistory};
-use super::flags::*;
+use super::{FlowLogic, Shell, ShellHistory};
 use super::flow_control::Statement;
 use super::status::*;
 use liner::{Buffer, Context};
-use smallvec::SmallVec;
 use std::env;
-use std::error::Error;
 use std::fs::File;
-use std::io::{stdout, Write};
 use std::io::ErrorKind;
-use std::iter::{self, FromIterator};
+use std::iter;
 use std::path::Path;
 use std::process;
 
-const MAN_ION: &'static str = r#"NAME
+pub const MAN_ION: &'static str = r#"NAME
     ion - ion shell
 
 SYNOPSIS
@@ -44,9 +40,6 @@ OPTIONS
 "#;
 
 pub trait Binary {
-    /// Launches the shell, parses arguments, and then diverges into one of the `execution`
-    /// paths.
-    fn main(self);
     /// Parses and executes the arguments that were supplied to the shell.
     fn execute_arguments<A: Iterator<Item = String>>(&mut self, args: A);
     /// Creates an interactive session that reads from a prompt provided by
@@ -192,47 +185,6 @@ impl Binary for Shell {
         }
     }
 
-    fn main(mut self) {
-        let mut args = env::args().skip(1);
-        while let Some(path) = args.next() {
-            match path.as_str() {
-                "-n" | "--no-execute" => {
-                    self.flags |= NO_EXEC;
-                    continue;
-                }
-                "-c" => self.execute_arguments(args),
-                "-v" | "--version" => self.display_version(),
-                "-h" | "--help" => {
-                    let stdout = stdout();
-                    let mut stdout = stdout.lock();
-                    match stdout
-                        .write_all(MAN_ION.as_bytes())
-                        .and_then(|_| stdout.flush())
-                    {
-                        Ok(_) => return,
-                        Err(err) => panic!("{}", err.description().to_owned()),
-                    }
-                }
-                _ => {
-                    let mut array = SmallVec::from_iter(Some(path.clone().into()));
-                    for arg in args {
-                        array.push(arg.into());
-                    }
-                    self.variables.set_array("args", array);
-                    if let Err(err) = self.execute_script(&path) {
-                        eprintln!("ion: {}", err);
-                    }
-                }
-            }
-
-            self.wait_for_background();
-            let previous_status = self.previous_status;
-            self.exit(previous_status);
-        }
-
-        self.execute_interactive();
-    }
-
     fn display_version(&self) {
         println!("{}", include!(concat!(env!("OUT_DIR"), "/version_string")));
         process::exit(0);
diff --git a/src/lib/shell/directory_stack.rs b/src/lib/shell/directory_stack.rs
index 5eefe5c57c5ee84eac3efcccf80ed1bedc69d5c4..bda81db543763ad86bcd17eceedc1acb7e7913bd 100644
--- a/src/lib/shell/directory_stack.rs
+++ b/src/lib/shell/directory_stack.rs
@@ -1,9 +1,9 @@
-use super::status::{FAILURE, SUCCESS};
-use super::variables::Variables;
 use std::borrow::Cow;
 use std::collections::VecDeque;
 use std::env::{current_dir, home_dir, set_current_dir};
 use std::path::PathBuf;
+use super::status::{FAILURE, SUCCESS};
+use super::variables::Variables;
 
 pub struct DirectoryStack {
     dirs: VecDeque<PathBuf>, // The top is always the current directory
diff --git a/src/lib/shell/pipe_exec/command_not_found.rs b/src/lib/shell/fork_function.rs
similarity index 51%
rename from src/lib/shell/pipe_exec/command_not_found.rs
rename to src/lib/shell/fork_function.rs
index 888a39b90564d8c2a4d8a4919ad716cc8984248e..1fe74d37520d00ea27262f84b2f326cc1f9e23ba 100644
--- a/src/lib/shell/pipe_exec/command_not_found.rs
+++ b/src/lib/shell/fork_function.rs
@@ -1,17 +1,23 @@
-use super::super::{Capture, Function, Shell};
+use super::{Capture, Function, Shell};
 use std::process;
 use sys;
 
 pub(crate) fn command_not_found(shell: &mut Shell, command: &str) -> bool {
-    let function = match shell.functions.get("COMMAND_NOT_FOUND") {
+    fork_function(shell, "COMMAND_NOT_FOUND", &["ion", &command])
+}
+
+/// High-level function for executing a function programmatically.
+/// NOTE: Always add "ion" as a first argument in `args`.
+pub fn fork_function(shell: &mut Shell, fn_name: &str, args: &[&str]) -> bool {
+    let function = match shell.functions.get(fn_name) {
         Some(func) => func as *const Function,
         None => return false,
     };
 
     if let Err(err) = shell.fork(Capture::None, |child| {
-        let result = unsafe { function.read() }.execute(child, &["ion", command]);
+        let result = unsafe { function.read() }.execute(child, args);
         if let Err(err) = result {
-            eprintln!("ion: COMMAND_NOT_FOUND function call: {}", err);
+            eprintln!("ion: {} function call: {}", fn_name, err);
         }
     }) {
         eprintln!("ion: fork error: {}", err);
diff --git a/src/lib/shell/mod.rs b/src/lib/shell/mod.rs
index c483cf76e3dddbb05ee55ec329397dd5993be626..dfdf25cb31a5180931ccc7b4de9792c9dfb6fcd4 100644
--- a/src/lib/shell/mod.rs
+++ b/src/lib/shell/mod.rs
@@ -1,19 +1,20 @@
 mod assignments;
-mod binary;
 mod completer;
 mod flow;
 mod fork;
 mod history;
 mod job;
-mod pipe_exec;
+pub mod flags;
+pub mod fork_function;
+pub mod status;
+pub mod variables;
+pub(crate) mod binary;
 pub(crate) mod colors;
 pub(crate) mod directory_stack;
-pub mod flags;
-pub(crate) mod plugins;
 pub(crate) mod flow_control;
+pub(crate) mod pipe_exec;
+pub(crate) mod plugins;
 pub(crate) mod signals;
-pub mod status;
-pub mod variables;
 
 pub use self::binary::Binary;
 pub(crate) use self::flow::FlowLogic;
@@ -70,7 +71,7 @@ pub struct Shell {
     /// Note that the context is only available in an interactive session.
     pub(crate) context: Option<Context>,
     /// Contains the aliases, strings, and array variable maps.
-    pub(crate) variables: Variables,
+    pub variables: Variables,
     /// Contains the current state of flow control parameters.
     flow_control: FlowControl,
     /// Contains the directory stack parameters.
@@ -202,7 +203,8 @@ impl<'a> Shell {
         }
     }
 
-    pub(crate) fn exit(&mut self, status: i32) -> ! {
+    /// Cleanly exit ion
+    pub fn exit(&mut self, status: i32) -> ! {
         self.prep_for_exit();
         process::exit(status);
     }
diff --git a/src/lib/shell/pipe_exec/job_control.rs b/src/lib/shell/pipe_exec/job_control.rs
index 5fcd3d086b3c2cf433b78d7985e0aee2926e7079..b2d26a3f895eac03ac51dff2103971777d8e5630 100644
--- a/src/lib/shell/pipe_exec/job_control.rs
+++ b/src/lib/shell/pipe_exec/job_control.rs
@@ -20,7 +20,7 @@ pub(crate) fn set_foreground_as(pid: u32) {
     signals::unblock();
 }
 
-pub(crate) trait JobControl {
+pub trait JobControl {
     /// Waits for background jobs to finish before returning.
     fn wait_for_background(&mut self);
     /// Takes a background tasks's PID and whether or not it needs to be continued; resumes the
diff --git a/src/lib/shell/pipe_exec/mod.rs b/src/lib/shell/pipe_exec/mod.rs
index 26529d11283e94f973ff9c4deb14334a3fc99323..9af781d700091c31620d15b9e9007ad03f76cd27 100644
--- a/src/lib/shell/pipe_exec/mod.rs
+++ b/src/lib/shell/pipe_exec/mod.rs
@@ -5,30 +5,29 @@
 //! the background, handling pipeline and conditional operators, and
 //! std{in,out,err} redirections.
 
-mod command_not_found;
-pub mod foreground;
 mod fork;
-pub mod job_control;
 mod streams;
+pub mod foreground;
+pub mod job_control;
 
-use self::command_not_found::command_not_found;
+use builtins::{self, BuiltinFunction};
+use parser::pipelines::{Input, PipeItem, Pipeline, RedirectFrom, Redirection};
 use self::fork::fork_pipe;
 use self::job_control::{JobControl, ProcessState};
 use self::streams::{duplicate_streams, redir, redirect_streams};
-use super::{JobKind, Shell};
-use super::flags::*;
-use super::flow_control::FunctionError;
-use super::job::{RefinedJob, TeeItem};
-use super::signals::{self, SignalHandler};
-use super::status::*;
-use builtins::{self, BuiltinFunction};
-use parser::pipelines::{Input, PipeItem, Pipeline, RedirectFrom, Redirection};
 use std::fs::{File, OpenOptions};
 use std::io::{self, Error, Write};
 use std::iter;
 use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
 use std::path::Path;
 use std::process::{self, exit};
+use super::flags::*;
+use super::flow_control::FunctionError;
+use super::fork_function::command_not_found;
+use super::job::{RefinedJob, TeeItem};
+use super::signals::{self, SignalHandler};
+use super::status::*;
+use super::{JobKind, Shell};
 use sys;
 
 type RefinedItem = (RefinedJob, JobKind, Vec<Redirection>, Vec<Input>);
diff --git a/src/main.rs b/src/main.rs
index e5cd2d1d228672ac69e05df4c6c890aa604f361f..ad618c3c28e8a50a18fc629a7f14e04c56762c86 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,14 +1,59 @@
 extern crate ion_shell;
+extern crate smallvec;
 
+use ion_shell::JobControl;
+use ion_shell::MAN_ION;
+use ion_shell::flags::NO_EXEC;
 use ion_shell::{Binary, ShellBuilder};
+use smallvec::SmallVec;
+use std::env;
+use std::error::Error;
+use std::io::{stdout, Write};
+use std::iter::FromIterator;
 
 fn main() {
-    ShellBuilder::new()
+    let mut shell = ShellBuilder::new()
         .install_signal_handler()
         .block_signals()
         .set_unique_pid()
-        .as_binary()
-        .main();
-}
+        .as_binary();
+
+    let mut args = env::args().skip(1);
+    while let Some(path) = args.next() {
+        match path.as_str() {
+            "-n" | "--no-execute" => {
+                shell.flags |= NO_EXEC;
+                continue;
+            }
+            "-c" => shell.execute_arguments(args),
+            "-v" | "--version" => shell.display_version(),
+            "-h" | "--help" => {
+                let stdout = stdout();
+                let mut stdout = stdout.lock();
+                match stdout
+                    .write_all(MAN_ION.as_bytes())
+                    .and_then(|_| stdout.flush())
+                {
+                    Ok(_) => return,
+                    Err(err) => panic!("{}", err.description().to_owned()),
+                }
+            }
+            _ => {
+                let mut array = SmallVec::from_iter(Some(path.clone().into()));
+                for arg in args {
+                    array.push(arg.into());
+                }
+                shell.variables.set_array("args", array);
+                if let Err(err) = shell.execute_script(&path) {
+                    eprintln!("ion: {}", err);
+                }
+            }
+        }
 
-// TODO: The `Binary` / `main()` logic should be implemented here, and not within the library.
\ No newline at end of file
+        shell.wait_for_background();
+        let previous_status = shell.previous_status;
+        shell.exit(previous_status);
+    }
+
+    shell.execute_interactive();
+}