diff --git a/.gitignore b/.gitignore
index fac93dd..79dd381 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
-.idea
+/.idea
 
 # Binaries for programs and plugins
 *.exe
@@ -13,4 +13,7 @@
 # Output of the go coverage tool, specifically when used with LiteIDE
 *.out
 
-image
+/build
+/_vendor*
+/vendor
+/.image
diff --git a/Gopkg.lock b/Gopkg.lock
new file mode 100644
index 0000000..5166b9e
--- /dev/null
+++ b/Gopkg.lock
@@ -0,0 +1,104 @@
+# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
+
+
+[[projects]]
+  name = "github.com/Microsoft/go-winio"
+  packages = ["."]
+  revision = "7da180ee92d8bd8bb8c37fc560e673e6557c392f"
+  version = "v0.4.7"
+
+[[projects]]
+  name = "github.com/docker/distribution"
+  packages = [
+    "digest",
+    "reference"
+  ]
+  revision = "48294d928ced5dd9b378f7fd7c6f5da3ff3f2c89"
+  version = "v2.6.2"
+
+[[projects]]
+  name = "github.com/docker/docker"
+  packages = [
+    "api/types",
+    "api/types/blkiodev",
+    "api/types/container",
+    "api/types/events",
+    "api/types/filters",
+    "api/types/mount",
+    "api/types/network",
+    "api/types/reference",
+    "api/types/registry",
+    "api/types/strslice",
+    "api/types/swarm",
+    "api/types/time",
+    "api/types/versions",
+    "api/types/volume",
+    "client",
+    "pkg/tlsconfig"
+  ]
+  revision = "092cba3727bb9b4a2f0e922cd6c0f93ea270e363"
+  version = "v1.13.1"
+
+[[projects]]
+  name = "github.com/docker/go-connections"
+  packages = [
+    "nat",
+    "sockets",
+    "tlsconfig"
+  ]
+  revision = "3ede32e2033de7505e6500d6c868c2b9ed9f169d"
+  version = "v0.3.0"
+
+[[projects]]
+  name = "github.com/docker/go-units"
+  packages = ["."]
+  revision = "47565b4f722fb6ceae66b95f853feed578a4a51c"
+  version = "v0.3.3"
+
+[[projects]]
+  branch = "master"
+  name = "github.com/jroimartin/gocui"
+  packages = ["."]
+  revision = "c055c87ae801372cd74a0839b972db4f7697ae5f"
+
+[[projects]]
+  name = "github.com/mattn/go-runewidth"
+  packages = ["."]
+  revision = "9e777a8366cce605130a531d2cd6363d07ad7317"
+  version = "v0.0.2"
+
+[[projects]]
+  branch = "master"
+  name = "github.com/nsf/termbox-go"
+  packages = ["."]
+  revision = "21a4d435a86280a2927985fd6296de56cbce453e"
+
+[[projects]]
+  name = "github.com/pkg/errors"
+  packages = ["."]
+  revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
+  version = "v0.8.0"
+
+[[projects]]
+  branch = "master"
+  name = "golang.org/x/net"
+  packages = [
+    "context",
+    "context/ctxhttp",
+    "internal/socks",
+    "proxy"
+  ]
+  revision = "1e491301e022f8f977054da4c2d852decd59571f"
+
+[[projects]]
+  branch = "master"
+  name = "golang.org/x/sys"
+  packages = ["windows"]
+  revision = "c11f84a56e43e20a78cee75a7c034031ecf57d1f"
+
+[solve-meta]
+  analyzer-name = "dep"
+  analyzer-version = 1
+  inputs-digest = "73c0fae1988538f4def02f2bd28830793264b4260b25d078b561413085a81845"
+  solver-name = "gps-cdcl"
+  solver-version = 1
diff --git a/Gopkg.toml b/Gopkg.toml
new file mode 100644
index 0000000..2fa2eba
--- /dev/null
+++ b/Gopkg.toml
@@ -0,0 +1,43 @@
+# Gopkg.toml example
+#
+# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
+# for detailed Gopkg.toml documentation.
+#
+# required = ["github.com/user/thing/cmd/thing"]
+# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
+#
+# [[constraint]]
+#   name = "github.com/user/project"
+#   version = "1.0.0"
+#
+# [[constraint]]
+#   name = "github.com/user/project2"
+#   branch = "dev"
+#   source = "github.com/myfork/project2"
+#
+# [[override]]
+#   name = "github.com/x/y"
+#   version = "2.4.0"
+#
+# [prune]
+#   non-go = false
+#   go-tests = true
+#   unused-packages = true
+
+
+[[constraint]]
+  name = "github.com/docker/docker"
+  version = "1.13.1"
+
+[[constraint]]
+  name = "github.com/jroimartin/gocui"
+  # version = "0.3.0"
+  branch = "master"
+
+[[constraint]]
+  branch = "master"
+  name = "golang.org/x/net"
+
+[prune]
+  go-tests = true
+  unused-packages = true
diff --git a/Makefile b/Makefile
index 36ef9db..796f6da 100644
--- a/Makefile
+++ b/Makefile
@@ -1,12 +1,34 @@
-SHELL := /bin/bash
-.DEFAULT_GOAL := run
-.PHONY: run
+BIN = die
 
-run:
-	 go run main.go \
-	 		filechangeinfo.go  \
-			filenode.go \
-			filetree.go \
-			tar_read.go \
-			filetreeview.go \
-			layerview.go
+all: clean build
+
+run: build
+	./build/$(BIN)
+
+build: deps
+	go build -o build/$(BIN) ./cmd/...
+
+install: deps
+	go install ./...
+
+deps:
+	command -v dep >/dev/null || go get -u github.com/golang/dep/cmd/dep
+	dep ensure
+
+test: build
+	@! git grep tcell -- ':!tui/' ':!Gopkg.lock' ':!Gopkg.toml' ':!Makefile'
+	go test -v ./...
+
+lint: lintdeps build
+	golint -set_exit_status $$(go list ./... | grep -v /vendor/)
+
+lintdeps:
+	go get -d -v -t ./...
+	command -v golint >/dev/null || go get -u github.com/golang/lint/golint
+
+clean:
+	rm -rf build
+	rm -rf vendor
+	go clean
+
+.PHONY: build install deps test lint lintdeps clean
\ No newline at end of file
diff --git a/cmd/die/main.go b/cmd/die/main.go
new file mode 100644
index 0000000..ab39ead
--- /dev/null
+++ b/cmd/die/main.go
@@ -0,0 +1,25 @@
+package main
+
+import (
+	"os"
+	"github.com/wagoodman/docker-image-explorer/image"
+	"github.com/wagoodman/docker-image-explorer/ui"
+)
+
+const name = "die"
+const version = "v0.0.0"
+const author = "wagoodman"
+
+func main() {
+	os.Exit(run(os.Args))
+}
+
+
+func run(args []string) int {
+	image.WriteImage()
+	manifest, refTrees := image.InitializeData()
+
+	ui.Run(manifest, refTrees)
+	return 0
+}
+
diff --git a/filechangeinfo.go b/filetree/changeinfo.go
similarity index 80%
rename from filechangeinfo.go
rename to filetree/changeinfo.go
index faa85ba..f1b19ba 100644
--- a/filechangeinfo.go
+++ b/filetree/changeinfo.go
@@ -1,4 +1,4 @@
-package main
+package filetree
 
 import (
 	"bytes"
@@ -6,10 +6,10 @@ import (
 )
 
 type FileChangeInfo struct {
-	path     string
-	typeflag byte
-	md5sum   [16]byte
-	diffType DiffType
+	Path     string
+	Typeflag byte
+	MD5sum   [16]byte
+	DiffType DiffType
 }
 
 type DiffType int
@@ -51,8 +51,8 @@ func (a *FileChangeInfo) getDiffType(b *FileChangeInfo) DiffType {
 	if a == nil || b == nil {
 		return Changed
 	}
-	if a.typeflag == b.typeflag {
-		if bytes.Compare(a.md5sum[:], b.md5sum[:]) == 0 {
+	if a.Typeflag == b.Typeflag {
+		if bytes.Compare(a.MD5sum[:], b.MD5sum[:]) == 0 {
 			return Unchanged
 		}
 	}
diff --git a/filechangeinfo_test.go b/filetree/changeinfo_test.go
similarity index 62%
rename from filechangeinfo_test.go
rename to filetree/changeinfo_test.go
index a1152ff..1892ac7 100644
--- a/filechangeinfo_test.go
+++ b/filetree/changeinfo_test.go
@@ -1,4 +1,4 @@
-package main
+package filetree
 
 import (
 	"fmt"
@@ -6,9 +6,9 @@ import (
 )
 
 func TestAssignDiffType(t *testing.T) {
-	tree := NewTree()
+	tree := NewFileTree()
 	tree.AddPath("/usr", BlankFileChangeInfo("/usr", Changed))
-	if tree.root.children["usr"].data.diffType != Changed {
+	if tree.Root.Children["usr"].Data.DiffType != Changed {
 		t.Fail()
 	}
 }
@@ -29,25 +29,25 @@ func TestMergeDiffTypes(t *testing.T) {
 }
 
 func TestDiffTypeFromChildren(t *testing.T) {
-	tree := NewTree()
+	tree := NewFileTree()
 	tree.AddPath("/usr", BlankFileChangeInfo("/usr", Unchanged))
 	info1 := BlankFileChangeInfo("/usr/bin", Added)
 	tree.AddPath("/usr/bin", info1)
 	info2 := BlankFileChangeInfo("/usr/bin2", Removed)
 	tree.AddPath("/usr/bin2", info2)
-	tree.root.children["usr"].deriveDiffType(Unchanged)
-	if tree.root.children["usr"].data.diffType != Changed {
-		t.Errorf("Expected Changed but got %v", tree.root.children["usr"].data.diffType)
+	tree.Root.Children["usr"].deriveDiffType(Unchanged)
+	if tree.Root.Children["usr"].Data.DiffType != Changed {
+		t.Errorf("Expected Changed but got %v", tree.Root.Children["usr"].Data.DiffType)
 	}
 }
 
 func AssertDiffType(node *FileNode, expectedDiffType DiffType, t *testing.T) error {
-	if node.data == nil {
-		t.Errorf("Expected *FileChangeInfo but got nil at path %s", node.Path())
-		return fmt.Errorf("expected *FileChangeInfo but got nil at path %s", node.Path())
+	if node.Data == nil {
+		t.Errorf("Expected *FileChangeInfo but got nil at Path %s", node.Path())
+		return fmt.Errorf("expected *FileChangeInfo but got nil at Path %s", node.Path())
 	}
-	if node.data.diffType != expectedDiffType {
-		t.Errorf("Expecting node at %s to have DiffType %v, but had %v", node.Path(), expectedDiffType, node.data.diffType)
+	if node.Data.DiffType != expectedDiffType {
+		t.Errorf("Expecting node at %s to have DiffType %v, but had %v", node.Path(), expectedDiffType, node.Data.DiffType)
 		return fmt.Errorf("Assertion failed")
 	}
 	return nil
@@ -55,10 +55,10 @@ func AssertDiffType(node *FileNode, expectedDiffType DiffType, t *testing.T) err
 
 func BlankFileChangeInfo(path string, diffType DiffType) (f *FileChangeInfo) {
 	result := FileChangeInfo{
-		path:     path,
-		typeflag: 1,
-		md5sum:   [16]byte{1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0},
-		diffType: diffType,
+		Path:     path,
+		Typeflag: 1,
+		MD5sum:   [16]byte{1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0},
+		DiffType: diffType,
 	}
 	return &result
 }
diff --git a/filenode.go b/filetree/node.go
similarity index 69%
rename from filenode.go
rename to filetree/node.go
index ec06eef..394cf93 100644
--- a/filenode.go
+++ b/filetree/node.go
@@ -1,4 +1,4 @@
-package main
+package filetree
 
 import (
 	"sort"
@@ -6,25 +6,25 @@ import (
 )
 
 type FileNode struct {
-	tree      *FileTree
-	parent    *FileNode
-	name      string
-	collapsed bool
-	data      *FileChangeInfo
-	children  map[string]*FileNode
+	Tree      *FileTree
+	Parent    *FileNode
+	Name      string
+	Collapsed bool
+	Data      *FileChangeInfo
+	Children  map[string]*FileNode
 }
 
 func NewNode(parent *FileNode, name string, data *FileChangeInfo) (node *FileNode) {
 	node = new(FileNode)
-	node.name = name
+	node.Name = name
 	if data == nil {
 		data = &FileChangeInfo{}
 	}
-	node.data = data
-	node.children = make(map[string]*FileNode)
-	node.parent = parent
+	node.Data = data
+	node.Children = make(map[string]*FileNode)
+	node.Parent = parent
 	if parent != nil {
-		node.tree = parent.tree
+		node.Tree = parent.Tree
 	}
 	return node
 }
@@ -33,46 +33,46 @@ func (node *FileNode) Copy() *FileNode {
 	// newNode := new(FileNode)
 	// *newNode = *node
 	// return newNode
-	newNode := NewNode(node.parent, node.name, node.data)
-	for name, child := range node.children {
-		newNode.children[name] = child.Copy()
+	newNode := NewNode(node.Parent, node.Name, node.Data)
+	for name, child := range node.Children {
+		newNode.Children[name] = child.Copy()
 	}
 	return newNode
 }
 
 func (node *FileNode) AddChild(name string, data *FileChangeInfo) (child *FileNode) {
 	child = NewNode(node, name, data)
-	if node.children[name] != nil {
+	if node.Children[name] != nil {
 		// tree node already exists, replace the payload, keep the children
-		node.children[name].data = data
+		node.Children[name].Data = data
 	} else {
-		node.children[name] = child
-		node.tree.size++
+		node.Children[name] = child
+		node.Tree.Size++
 	}
 	return child
 }
 
 func (node *FileNode) Remove() error {
-	for _, child := range node.children {
+	for _, child := range node.Children {
 		child.Remove()
 	}
-	delete(node.parent.children, node.name)
-	node.tree.size--
+	delete(node.Parent.Children, node.Name)
+	node.Tree.Size--
 	return nil
 }
 
 func (node *FileNode) String() string {
-	return node.name
+	return node.Name
 }
 
 func (node *FileNode) Visit(visiter Visiter) error {
 	var keys []string
-	for key := range node.children {
+	for key := range node.Children {
 		keys = append(keys, key)
 	}
 	sort.Strings(keys)
 	for _, name := range keys {
-		child := node.children[name]
+		child := node.Children[name]
 		err := child.Visit(visiter)
 		if err != nil {
 			return err
@@ -88,12 +88,12 @@ func (node *FileNode) VisitDepthParentFirst(visiter Visiter, evaluator VisitEval
 	}
 
 	var keys []string
-	for key := range node.children {
+	for key := range node.Children {
 		keys = append(keys, key)
 	}
 	sort.Strings(keys)
 	for _, name := range keys {
-		child := node.children[name]
+		child := node.Children[name]
 		if evaluator == nil || !evaluator(node) {
 			continue
 		}
@@ -106,31 +106,31 @@ func (node *FileNode) VisitDepthParentFirst(visiter Visiter, evaluator VisitEval
 }
 
 func (node *FileNode) IsWhiteout() bool {
-	return strings.HasPrefix(node.name, whiteoutPrefix)
+	return strings.HasPrefix(node.Name, whiteoutPrefix)
 }
 
 func (node *FileNode) Path() string {
 	path := []string{}
 	curNode := node
 	for {
-		if curNode.parent == nil {
+		if curNode.Parent == nil {
 			break
 		}
 
-		name := curNode.name
+		name := curNode.Name
 		if curNode == node {
 			// white out prefixes are fictitious on leaf nodes
 			name = strings.TrimPrefix(name, whiteoutPrefix)
 		}
 
 		path = append([]string{name}, path...)
-		curNode = curNode.parent
+		curNode = curNode.Parent
 	}
 	return "/" + strings.Join(path, "/")
 }
 
 func (node *FileNode) IsLeaf() bool {
-	return len(node.children) == 0
+	return len(node.Children) == 0
 }
 
 func (node *FileNode) deriveDiffType(diffType DiffType) error {
@@ -143,9 +143,9 @@ func (node *FileNode) deriveDiffType(diffType DiffType) error {
 	}
 	myDiffType := diffType
 
-	for _, v := range node.children {
-		vData := v.data
-		myDiffType = myDiffType.merge(vData.diffType)
+	for _, v := range node.Children {
+		vData := v.Data
+		myDiffType = myDiffType.merge(vData.DiffType)
 
 	}
 	node.AssignDiffType(myDiffType)
@@ -156,7 +156,7 @@ func (node *FileNode) AssignDiffType(diffType DiffType) error {
 	if node.Path() == "/" {
 		return nil
 	}
-	node.data.diffType = diffType
+	node.Data.DiffType = diffType
 	return nil
 }
 
@@ -177,10 +177,10 @@ func (a *FileNode) compare(b *FileNode) DiffType {
 	if b.IsWhiteout() {
 		return Removed
 	}
-	if a.name != b.name {
+	if a.Name != b.Name {
 		panic("comparing mismatched nodes")
 	}
 	// TODO: fails on nil
 
-	return a.data.getDiffType(b.data)
+	return a.Data.getDiffType(b.Data)
 }
diff --git a/filenode_test.go b/filetree/node_test.go
similarity index 61%
rename from filenode_test.go
rename to filetree/node_test.go
index 910912c..17fa86b 100644
--- a/filenode_test.go
+++ b/filetree/node_test.go
@@ -1,49 +1,49 @@
-package main
+package filetree
 
 import "testing"
 
 func TestAddChild(t *testing.T) {
 	var expected, actual int
-	tree := NewTree()
+	tree := NewFileTree()
 
 	payload := FileChangeInfo{
-		path: "stufffffs",
+		Path: "stufffffs",
 	}
 
-	one := tree.Root().AddChild("first node!", &payload)
+	one := tree.Root.AddChild("first node!", &payload)
 
-	two := tree.Root().AddChild("nil node!", nil)
+	two := tree.Root.AddChild("nil node!", nil)
 
-	tree.Root().AddChild("third node!", nil)
+	tree.Root.AddChild("third node!", nil)
 	two.AddChild("forth, one level down...", nil)
 	two.AddChild("fifth, one level down...", nil)
 	two.AddChild("fifth, one level down...", nil)
 
-	expected, actual = 5, tree.size
+	expected, actual = 5, tree.Size
 	if expected != actual {
 		t.Errorf("Expected a tree size of %d got %d.", expected, actual)
 	}
 
-	expected, actual = 2, len(two.children)
+	expected, actual = 2, len(two.Children)
 	if expected != actual {
 		t.Errorf("Expected 'twos' number of children to be %d got %d.", expected, actual)
 	}
 
-	expected, actual = 3, len(tree.Root().children)
+	expected, actual = 3, len(tree.Root.Children)
 	if expected != actual {
 		t.Errorf("Expected 'twos' number of children to be %d got %d.", expected, actual)
 	}
 
 	expectedFC := &FileChangeInfo{
-		path: "stufffffs",
+		Path: "stufffffs",
 	}
-	actualFC := one.data
+	actualFC := one.Data
 	if *expectedFC != *actualFC {
 		t.Errorf("Expected 'ones' payload to be %+v got %+v.", expectedFC, actualFC)
 	}
 
-	if *two.data != *new(FileChangeInfo) {
-		t.Errorf("Expected 'twos' payload to be nil got %d.", two.data)
+	if *two.Data != *new(FileChangeInfo) {
+		t.Errorf("Expected 'twos' payload to be nil got %d.", two.Data)
 	}
 
 }
@@ -51,32 +51,32 @@ func TestAddChild(t *testing.T) {
 func TestRemoveChild(t *testing.T) {
 	var expected, actual int
 
-	tree := NewTree()
-	tree.Root().AddChild("first", nil)
-	two := tree.Root().AddChild("nil", nil)
-	tree.Root().AddChild("third", nil)
+	tree := NewFileTree()
+	tree.Root.AddChild("first", nil)
+	two := tree.Root.AddChild("nil", nil)
+	tree.Root.AddChild("third", nil)
 	forth := two.AddChild("forth", nil)
 	two.AddChild("fifth", nil)
 
 	forth.Remove()
 
-	expected, actual = 4, tree.size
+	expected, actual = 4, tree.Size
 	if expected != actual {
 		t.Errorf("Expected a tree size of %d got %d.", expected, actual)
 	}
 
-	if tree.Root().children["forth"] != nil {
+	if tree.Root.Children["forth"] != nil {
 		t.Errorf("Expected 'forth' node to be deleted.")
 	}
 
 	two.Remove()
 
-	expected, actual = 2, tree.size
+	expected, actual = 2, tree.Size
 	if expected != actual {
 		t.Errorf("Expected a tree size of %d got %d.", expected, actual)
 	}
 
-	if tree.Root().children["nil"] != nil {
+	if tree.Root.Children["nil"] != nil {
 		t.Errorf("Expected 'nil' node to be deleted.")
 	}
 
@@ -84,25 +84,25 @@ func TestRemoveChild(t *testing.T) {
 
 func TestPath(t *testing.T) {
 	expected := "/etc/nginx/nginx.conf"
-	tree := NewTree()
+	tree := NewFileTree()
 	node, _ := tree.AddPath(expected, nil)
 
 	actual := node.Path()
 	if expected != actual {
-		t.Errorf("Expected path '%s' got '%s'", expected, actual)
+		t.Errorf("Expected Path '%s' got '%s'", expected, actual)
 	}
 }
 
 func TestIsWhiteout(t *testing.T) {
-	tree1 := NewTree()
+	tree1 := NewFileTree()
 	p1, _ := tree1.AddPath("/etc/nginx/public1", nil)
 	p2, _ := tree1.AddPath("/etc/nginx/.wh.public2", nil)
 
 	if p1.IsWhiteout() != false {
-		t.Errorf("Expected path '%s' to **not** be a whiteout file", p1.name)
+		t.Errorf("Expected Path '%s' to **not** be a whiteout file", p1.Name)
 	}
 
 	if p2.IsWhiteout() != true {
-		t.Errorf("Expected path '%s' to be a whiteout file", p2.name)
+		t.Errorf("Expected Path '%s' to be a whiteout file", p2.Name)
 	}
 }
diff --git a/filetree.go b/filetree/tree.go
similarity index 79%
rename from filetree.go
rename to filetree/tree.go
index 3c87231..d1efd1a 100644
--- a/filetree.go
+++ b/filetree/tree.go
@@ -1,4 +1,4 @@
-package main
+package filetree
 
 import (
 	"errors"
@@ -19,24 +19,20 @@ const (
 )
 
 type FileTree struct {
-	root *FileNode
-	size int
-	name string
+	Root *FileNode
+	Size int
+	Name string
 }
 
-func NewTree() (tree *FileTree) {
+func NewFileTree() (tree *FileTree) {
 	tree = new(FileTree)
-	tree.size = 0
-	tree.root = new(FileNode)
-	tree.root.tree = tree
-	tree.root.children = make(map[string]*FileNode)
+	tree.Size = 0
+	tree.Root = new(FileNode)
+	tree.Root.Tree = tree
+	tree.Root.Children = make(map[string]*FileNode)
 	return tree
 }
 
-func (tree *FileTree) Root() *FileNode {
-	return tree.root
-}
-
 func (tree *FileTree) String() string {
 	var renderLine func(string, []bool, bool, bool) string
 	var walkTree func(*FileNode, []bool, int) string
@@ -67,16 +63,16 @@ func (tree *FileTree) String() string {
 	walkTree = func(node *FileNode, spaces []bool, depth int) string {
 		var result string
 		var keys []string
-		for key := range node.children {
+		for key := range node.Children {
 			keys = append(keys, key)
 		}
 		sort.Strings(keys)
 		for idx, name := range keys {
-			child := node.children[name]
-			last := idx == (len(node.children) - 1)
-			showCollapsed := child.collapsed && len(child.children) > 0
+			child := node.Children[name]
+			last := idx == (len(node.Children) - 1)
+			showCollapsed := child.Collapsed && len(child.Children) > 0
 			result += renderLine(child.String(), spaces, last, showCollapsed)
-			if len(child.children) > 0 && !child.collapsed {
+			if len(child.Children) > 0 && !child.Collapsed {
 				spacesChild := append(spaces, last)
 				result += walkTree(child, spacesChild, depth+1)
 			}
@@ -84,15 +80,15 @@ func (tree *FileTree) String() string {
 		return result
 	}
 
-	return "." + newLine + walkTree(tree.Root(), []bool{}, 0)
+	return "." + newLine + walkTree(tree.Root, []bool{}, 0)
 }
 
 func (tree *FileTree) Copy() *FileTree {
-	newTree := NewTree()
+	newTree := NewFileTree()
 	*newTree = *tree
-	newTree.root = tree.Root().Copy()
+	newTree.Root = tree.Root.Copy()
 	newTree.Visit(func(node *FileNode) error {
-		node.tree = newTree
+		node.Tree = newTree
 		return nil
 	})
 
@@ -103,11 +99,11 @@ type Visiter func(*FileNode) error
 type VisitEvaluator func(*FileNode) bool
 
 func (tree *FileTree) Visit(visiter Visiter) error {
-	return tree.root.Visit(visiter)
+	return tree.Root.Visit(visiter)
 }
 
 func (tree *FileTree) VisitDepthParentFirst(visiter Visiter, evaluator VisitEvaluator) error {
-	return tree.root.VisitDepthParentFirst(visiter, evaluator)
+	return tree.Root.VisitDepthParentFirst(visiter, evaluator)
 }
 
 func (tree *FileTree) Stack(upper *FileTree) error {
@@ -118,7 +114,7 @@ func (tree *FileTree) Stack(upper *FileTree) error {
 				return fmt.Errorf("Cannot remove node %s: %v", node.Path(), err.Error())
 			}
 		} else {
-			newNode, err := tree.AddPath(node.Path(), node.data)
+			newNode, err := tree.AddPath(node.Path(), node.Data)
 			if err != nil {
 				return fmt.Errorf("Cannot add node %s: %v", newNode.Path(), err.Error())
 			}
@@ -130,38 +126,38 @@ func (tree *FileTree) Stack(upper *FileTree) error {
 
 func (tree *FileTree) GetNode(path string) (*FileNode, error) {
 	nodeNames := strings.Split(path, "/")
-	node := tree.Root()
+	node := tree.Root
 	for _, name := range nodeNames {
 		if name == "" {
 			continue
 		}
-		if node.children[name] == nil {
+		if node.Children[name] == nil {
 			return nil, errors.New("Path does not exist")
 		}
-		node = node.children[name]
+		node = node.Children[name]
 	}
 	return node, nil
 }
 
 func (tree *FileTree) AddPath(path string, data *FileChangeInfo) (*FileNode, error) {
 	nodeNames := strings.Split(path, "/")
-	node := tree.Root()
+	node := tree.Root
 	for idx, name := range nodeNames {
 		if name == "" {
 			continue
 		}
 		// find or create node
-		if node.children[name] != nil {
-			node = node.children[name]
+		if node.Children[name] != nil {
+			node = node.Children[name]
 		} else {
 			// don't attach the payload. The payload is destined for the
-			// path's end node, not any intermediary node.
+			// Path's end node, not any intermediary node.
 			node = node.AddChild(name, nil)
 		}
 
 		// attach payload to the last specified node
 		if idx == len(nodeNames)-1 {
-			node.data = data
+			node.Data = data
 		}
 
 	}
@@ -186,7 +182,7 @@ func (tree *FileTree) compare(upper *FileTree) error {
 		} else {
 			existingNode, _ := tree.GetNode(node.Path())
 			if existingNode == nil {
-				newNode, err := tree.AddPath(node.Path(), node.data)
+				newNode, err := tree.AddPath(node.Path(), node.Data)
 				fmt.Printf("added new node at %s\n", newNode.Path())
 				if err != nil {
 					return fmt.Errorf("Cannot add new node %s: %v", node.Path(), err.Error())
diff --git a/filetree_test.go b/filetree/tree_test.go
similarity index 80%
rename from filetree_test.go
rename to filetree/tree_test.go
index cf82ff0..67d6c2e 100644
--- a/filetree_test.go
+++ b/filetree/tree_test.go
@@ -1,4 +1,4 @@
-package main
+package filetree
 
 import (
 	"fmt"
@@ -6,10 +6,10 @@ import (
 )
 
 func TestPrintTree(t *testing.T) {
-	tree := NewTree()
-	tree.Root().AddChild("first node!", nil)
-	two := tree.Root().AddChild("second node!", nil)
-	tree.Root().AddChild("third node!", nil)
+	tree := NewFileTree()
+	tree.Root.AddChild("first node!", nil)
+	two := tree.Root.AddChild("second node!", nil)
+	tree.Root.AddChild("third node!", nil)
 	two.AddChild("forth, one level down...", nil)
 
 	expected := `.
@@ -27,7 +27,7 @@ func TestPrintTree(t *testing.T) {
 }
 
 func TestAddPath(t *testing.T) {
-	tree := NewTree()
+	tree := NewFileTree()
 	tree.AddPath("/etc/nginx/nginx.conf", nil)
 	tree.AddPath("/etc/nginx/public", nil)
 	tree.AddPath("/var/run/systemd", nil)
@@ -56,7 +56,7 @@ func TestAddPath(t *testing.T) {
 }
 
 func TestRemovePath(t *testing.T) {
-	tree := NewTree()
+	tree := NewFileTree()
 	tree.AddPath("/etc/nginx/nginx.conf", nil)
 	tree.AddPath("/etc/nginx/public", nil)
 	tree.AddPath("/var/run/systemd", nil)
@@ -87,10 +87,10 @@ func TestRemovePath(t *testing.T) {
 func TestStack(t *testing.T) {
 	payloadKey := "/var/run/systemd"
 	payloadValue := FileChangeInfo{
-		path: "yup",
+		Path: "yup",
 	}
 
-	tree1 := NewTree()
+	tree1 := NewFileTree()
 
 	tree1.AddPath("/etc/nginx/public", nil)
 	tree1.AddPath(payloadKey, nil)
@@ -98,7 +98,7 @@ func TestStack(t *testing.T) {
 	tree1.AddPath("/tmp", nil)
 	tree1.AddPath("/tmp/nonsense", nil)
 
-	tree2 := NewTree()
+	tree2 := NewFileTree()
 	// add new files
 	tree2.AddPath("/etc/nginx/nginx.conf", nil)
 	// modify current files
@@ -128,8 +128,8 @@ func TestStack(t *testing.T) {
 		t.Errorf("Expected '%s' to still exist, but it doesn't", payloadKey)
 	}
 
-	if *node.data != payloadValue {
-		t.Errorf("Expected '%s' value to be %+v but got %+v", payloadKey, payloadValue, node.data)
+	if *node.Data != payloadValue {
+		t.Errorf("Expected '%s' value to be %+v but got %+v", payloadKey, payloadValue, node.Data)
 	}
 
 	actual := tree1.String()
@@ -141,7 +141,7 @@ func TestStack(t *testing.T) {
 }
 
 func TestCopy(t *testing.T) {
-	tree := NewTree()
+	tree := NewFileTree()
 	tree.AddPath("/etc/nginx/nginx.conf", nil)
 	tree.AddPath("/etc/nginx/public", nil)
 	tree.AddPath("/var/run/systemd", nil)
@@ -162,8 +162,8 @@ func TestCopy(t *testing.T) {
         └── systemd
 `
 
-	newTree := tree.Copy()
-	actual := newTree.String()
+	NewFileTree := tree.Copy()
+	actual := NewFileTree.String()
 
 	if expected != actual {
 		t.Errorf("Expected tree string:\n--->%s<---\nGot:\n--->%s<---", expected, actual)
@@ -172,16 +172,16 @@ func TestCopy(t *testing.T) {
 }
 
 func TestCompareWithNoChanges(t *testing.T) {
-	lowerTree := NewTree()
-	upperTree := NewTree()
+	lowerTree := NewFileTree()
+	upperTree := NewFileTree()
 	paths := [...]string{"/etc", "/etc/sudoers", "/etc/hosts", "/usr/bin", "/usr/bin/bash", "/usr"}
 
 	for _, value := range paths {
 		fakeData := FileChangeInfo{
-			path:     value,
-			typeflag: 1,
-			md5sum:   [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
-			diffType: Unchanged,
+			Path:     value,
+			Typeflag: 1,
+			MD5sum:   [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+			DiffType: Unchanged,
 		}
 		lowerTree.AddPath(value, &fakeData)
 		upperTree.AddPath(value, &fakeData)
@@ -191,12 +191,12 @@ func TestCompareWithNoChanges(t *testing.T) {
 		if n.Path() == "/" {
 			return nil
 		}
-		if n.data == nil {
+		if n.Data == nil {
 			t.Errorf("Expected *FileChangeInfo but got nil")
 			return fmt.Errorf("expected *FileChangeInfo but got nil")
 		}
-		if (n.data.diffType) != Unchanged {
-			t.Errorf("Expecting node at %s to have DiffType unchanged, but had %v", n.Path(), n.data.diffType)
+		if (n.Data.DiffType) != Unchanged {
+			t.Errorf("Expecting node at %s to have DiffType unchanged, but had %v", n.Path(), n.Data.DiffType)
 		}
 		return nil
 	}
@@ -207,27 +207,27 @@ func TestCompareWithNoChanges(t *testing.T) {
 }
 
 func TestCompareWithAdds(t *testing.T) {
-	lowerTree := NewTree()
-	upperTree := NewTree()
+	lowerTree := NewFileTree()
+	upperTree := NewFileTree()
 	lowerPaths := [...]string{"/etc", "/etc/sudoers", "/usr", "/etc/hosts", "/usr/bin"}
 	upperPaths := [...]string{"/etc", "/etc/sudoers", "/usr", "/etc/hosts", "/usr/bin", "/usr/bin/bash"}
 
 	for _, value := range lowerPaths {
 		fakeData := FileChangeInfo{
-			path:     value,
-			typeflag: 1,
-			md5sum:   [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
-			diffType: Unchanged,
+			Path:     value,
+			Typeflag: 1,
+			MD5sum:   [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+			DiffType: Unchanged,
 		}
 		lowerTree.AddPath(value, &fakeData)
 	}
 
 	for _, value := range upperPaths {
 		fakeData := FileChangeInfo{
-			path:     value,
-			typeflag: 1,
-			md5sum:   [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
-			diffType: Unchanged,
+			Path:     value,
+			Typeflag: 1,
+			MD5sum:   [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+			DiffType: Unchanged,
 		}
 		upperTree.AddPath(value, &fakeData)
 	}
@@ -258,27 +258,27 @@ func TestCompareWithAdds(t *testing.T) {
 }
 
 func TestCompareWithChanges(t *testing.T) {
-	lowerTree := NewTree()
-	upperTree := NewTree()
+	lowerTree := NewFileTree()
+	upperTree := NewFileTree()
 	lowerPaths := [...]string{"/etc", "/usr", "/etc/hosts", "/etc/sudoers", "/usr/bin"}
 	upperPaths := [...]string{"/etc", "/usr", "/etc/hosts", "/etc/sudoers", "/usr/bin"}
 
 	for _, value := range lowerPaths {
 		fakeData := FileChangeInfo{
-			path:     value,
-			typeflag: 1,
-			md5sum:   [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
-			diffType: Unchanged,
+			Path:     value,
+			Typeflag: 1,
+			MD5sum:   [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+			DiffType: Unchanged,
 		}
 		lowerTree.AddPath(value, &fakeData)
 	}
 
 	for _, value := range upperPaths {
 		fakeData := FileChangeInfo{
-			path:     value,
-			typeflag: 1,
-			md5sum:   [16]byte{1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0},
-			diffType: Unchanged,
+			Path:     value,
+			Typeflag: 1,
+			MD5sum:   [16]byte{1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0},
+			DiffType: Unchanged,
 		}
 		upperTree.AddPath(value, &fakeData)
 	}
@@ -298,7 +298,7 @@ func TestCompareWithChanges(t *testing.T) {
 }
 
 func TestStackRange(t *testing.T) {
-	tree := NewTree()
+	tree := NewFileTree()
 	tree.AddPath("/etc/nginx/nginx.conf", nil)
 	tree.AddPath("/etc/nginx/public", nil)
 	tree.AddPath("/var/run/systemd", nil)
@@ -309,27 +309,27 @@ func TestStackRange(t *testing.T) {
 	tree.RemovePath("/var/run/bashful")
 	tree.RemovePath("/tmp")
 
-	lowerTree := NewTree()
-	upperTree := NewTree()
+	lowerTree := NewFileTree()
+	upperTree := NewFileTree()
 	lowerPaths := [...]string{"/etc", "/usr", "/etc/hosts", "/etc/sudoers", "/usr/bin"}
 	upperPaths := [...]string{"/etc", "/usr", "/etc/hosts", "/etc/sudoers", "/usr/bin"}
 
 	for _, value := range lowerPaths {
 		fakeData := FileChangeInfo{
-			path:     value,
-			typeflag: 1,
-			md5sum:   [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
-			diffType: Unchanged,
+			Path:     value,
+			Typeflag: 1,
+			MD5sum:   [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+			DiffType: Unchanged,
 		}
 		lowerTree.AddPath(value, &fakeData)
 	}
 
 	for _, value := range upperPaths {
 		fakeData := FileChangeInfo{
-			path:     value,
-			typeflag: 1,
-			md5sum:   [16]byte{1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0},
-			diffType: Unchanged,
+			Path:     value,
+			Typeflag: 1,
+			MD5sum:   [16]byte{1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0},
+			DiffType: Unchanged,
 		}
 		upperTree.AddPath(value, &fakeData)
 	}
diff --git a/filetreeview.go b/filetreeview.go
deleted file mode 100644
index 1a24fdf..0000000
--- a/filetreeview.go
+++ /dev/null
@@ -1,114 +0,0 @@
-package main
-
-import (
-	"fmt"
-
-	"github.com/jroimartin/gocui"
-)
-
-type FileTreeView struct {
-	name         string
-	gui          *gocui.Gui
-	view         *gocui.View
-	absTreeIndex uint
-	tree         *FileTree
-}
-
-func NewFileTreeView(name string, gui *gocui.Gui, view *gocui.View, tree *FileTree) (treeview *FileTreeView) {
-	treeview = new(FileTreeView)
-
-	// populate main fields
-	treeview.name = name
-	treeview.gui = gui
-	treeview.view = view
-	treeview.tree = tree
-
-	// set view options
-	treeview.view.Editable = false
-	treeview.view.Wrap = false
-	treeview.view.Highlight = true
-	treeview.view.SelBgColor = gocui.ColorGreen
-	treeview.view.SelFgColor = gocui.ColorBlack
-
-	treeview.render()
-
-	return treeview
-}
-
-func (view *FileTreeView) keybindings() error {
-	if err := view.gui.SetKeybinding(view.name, gocui.KeyArrowDown, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.cursorDown() }); err != nil {
-		return err
-	}
-	if err := view.gui.SetKeybinding(view.name, gocui.KeyArrowUp, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.cursorUp() }); err != nil {
-		return err
-	}
-	if err := view.gui.SetKeybinding(view.name, gocui.KeySpace, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.toggleCollapse() }); err != nil {
-		return err
-	}
-	return nil
-}
-
-// Mehh, this is just a bad method
-func (view *FileTreeView) reset(tree *FileTree) error {
-	view.tree = tree
-	view.view.SetCursor(0, 0)
-	view.absTreeIndex = 0
-	return view.render()
-}
-
-func (view *FileTreeView) cursorDown() error {
-	err := cursorDown(view.gui, view.view)
-	if err == nil {
-		view.absTreeIndex++
-	}
-	return nil
-}
-
-func (view *FileTreeView) cursorUp() error {
-	err := cursorUp(view.gui, view.view)
-	if err == nil {
-		view.absTreeIndex--
-	}
-	return nil
-}
-
-func (view *FileTreeView) getAbsPositionNode() (node *FileNode) {
-	var visiter func(*FileNode) error
-	var evaluator func(*FileNode) bool
-	var dfsCounter uint
-
-	visiter = func(curNode *FileNode) error {
-		if dfsCounter == view.absTreeIndex {
-			node = curNode
-		}
-		dfsCounter++
-		return nil
-	}
-
-	evaluator = func(curNode *FileNode) bool {
-		return !curNode.collapsed
-	}
-
-	err := view.tree.VisitDepthParentFirst(visiter, evaluator)
-	if err != nil {
-		// todo: you guessed it, check errors
-	}
-
-	return node
-}
-
-func (view *FileTreeView) toggleCollapse() error {
-	node := view.getAbsPositionNode()
-	node.collapsed = !node.collapsed
-	return view.render()
-}
-
-func (view *FileTreeView) render() error {
-	renderString := view.tree.String()
-	view.gui.Update(func(g *gocui.Gui) error {
-		view.view.Clear()
-		_, err := fmt.Fprintln(view.view, renderString)
-		return err
-	})
-	return nil
-}
diff --git a/image/image.go b/image/image.go
new file mode 100644
index 0000000..e49a4f4
--- /dev/null
+++ b/image/image.go
@@ -0,0 +1,104 @@
+package image
+
+import (
+	"io"
+	"os"
+	"bufio"
+	"github.com/docker/docker/client"
+	"fmt"
+	"encoding/json"
+	"golang.org/x/net/context"
+)
+
+func check(e error) {
+	if e != nil {
+		panic(e)
+	}
+}
+
+
+func saveImage(readCloser io.ReadCloser) {
+	defer readCloser.Close()
+
+	path := ".image"
+	if _, err := os.Stat(path); os.IsNotExist(err) {
+		os.Mkdir(path, 0755)
+	}
+
+	fo, err := os.Create(".image/cache.tar")
+	check(err)
+
+	defer func() {
+		if err := fo.Close(); err != nil {
+			panic(err)
+		}
+	}()
+	w := bufio.NewWriter(fo)
+
+	buf := make([]byte, 1024)
+	for {
+		n, err := readCloser.Read(buf)
+		if err != nil && err != io.EOF {
+			panic(err)
+		}
+		if n == 0 {
+			break
+		}
+
+		if _, err := w.Write(buf[:n]); err != nil {
+			panic(err)
+		}
+	}
+
+	if err = w.Flush(); err != nil {
+		panic(err)
+	}
+}
+
+
+func WriteImage() {
+	ctx := context.Background()
+	cli, err := client.NewEnvClient()
+	if err != nil {
+		panic(err)
+	}
+
+	// imageID := "golang:alpine"
+	imageID := "die-test:latest"
+
+	fmt.Println("Saving Image...")
+	readCloser, err := cli.ImageSave(ctx, []string{imageID})
+	check(err)
+	saveImage(readCloser)
+
+	for {
+		inspect, _, err := cli.ImageInspectWithRaw(ctx, imageID)
+		check(err)
+
+		history, err := cli.ImageHistory(ctx, imageID)
+		check(err)
+
+		historyStr, err := json.MarshalIndent(history, "", "  ")
+		check(err)
+
+		layerStr := ""
+		for idx, layer := range inspect.RootFS.Layers {
+			prefix := "├── "
+			if idx == len(inspect.RootFS.Layers)-1 {
+				prefix = "└── "
+			}
+			layerStr += fmt.Sprintf("%s%s\n", prefix, layer)
+		}
+
+		fmt.Printf("Image: %s\nId: %s\nParent: %s\nLayers: %d\n%sHistory: %s\n", imageID, inspect.ID, inspect.Parent, len(inspect.RootFS.Layers), layerStr, historyStr)
+
+		fmt.Println("\n")
+
+		if inspect.Parent == "" {
+			break
+		} else {
+			imageID = inspect.Parent
+		}
+	}
+	fmt.Println("See './.image' for the cached image tar")
+}
diff --git a/tar_read.go b/image/tar_read.go
similarity index 70%
rename from tar_read.go
rename to image/tar_read.go
index 68cdcff..7b9cca5 100644
--- a/tar_read.go
+++ b/image/tar_read.go
@@ -1,4 +1,4 @@
-package main
+package image
 
 import (
 	"archive/tar"
@@ -9,10 +9,12 @@ import (
 	"io"
 	"os"
 	"strings"
+
+	"github.com/wagoodman/docker-image-explorer/filetree"
 )
 
-func initializeData() {
-	f, err := os.Open("image/cache.tar")
+func InitializeData() (*Manifest, []*filetree.FileTree) {
+	f, err := os.Open("./.image/cache.tar")
 	if err != nil {
 		fmt.Println(err)
 		os.Exit(1)
@@ -22,8 +24,8 @@ func initializeData() {
 	tarReader := tar.NewReader(f)
 	targetName := "manifest.json"
 	var manifest Manifest
-	var layerMap map[string]*FileTree
-	layerMap = make(map[string]*FileTree)
+	var layerMap map[string]*filetree.FileTree
+	layerMap = make(map[string]*filetree.FileTree)
 
 	for {
 		header, err := tarReader.Next()
@@ -49,14 +51,14 @@ func initializeData() {
 
 			if strings.HasSuffix(name, "layer.tar") {
 				fmt.Println("Containing:")
-				tree := NewTree()
-				tree.name = name
-				fmt.Printf("%s\n", tree.name)
+				tree := filetree.NewFileTree()
+				tree.Name = name
+				fmt.Printf("%s\n", tree.Name)
 				fileInfos := getFileList(tarReader, header)
 				for _, element := range fileInfos {
-					tree.AddPath(element.path, &element)
+					tree.AddPath(element.Path, &element)
 				}
-				layerMap[tree.name] = tree
+				layerMap[tree.Name] = tree
 			}
 		default:
 			fmt.Printf("%s : %c %s %s\n",
@@ -67,18 +69,17 @@ func initializeData() {
 			)
 		}
 	}
-	var trees []*FileTree
-	trees = make([]*FileTree, 0)
+	var trees []*filetree.FileTree
+	trees = make([]*filetree.FileTree, 0)
 	for _, treeName := range manifest.Layers {
 		trees = append(trees, layerMap[treeName])
 	}
 
-	data.manifest = &manifest
-	data.refTrees = trees
+	return &manifest, trees
 }
 
-func getFileList(parentReader *tar.Reader, h *tar.Header) []FileChangeInfo {
-	var files []FileChangeInfo
+func getFileList(parentReader *tar.Reader, h *tar.Header) []filetree.FileChangeInfo {
+	var files []filetree.FileChangeInfo
 	size := h.Size
 	tarredBytes := make([]byte, size)
 	_, err := parentReader.Read(tarredBytes)
@@ -121,12 +122,12 @@ func getFileList(parentReader *tar.Reader, h *tar.Header) []FileChangeInfo {
 	return files
 }
 
-func makeEntry(r *tar.Reader, h *tar.Header, path string) FileChangeInfo {
+func makeEntry(r *tar.Reader, h *tar.Header, path string) filetree.FileChangeInfo {
 	if h.Typeflag == tar.TypeDir {
-		return FileChangeInfo{
-			path:     path,
-			typeflag: h.Typeflag,
-			md5sum:   [16]byte{},
+		return filetree.FileChangeInfo{
+			Path:     path,
+			Typeflag: h.Typeflag,
+			MD5sum:   [16]byte{},
 		}
 	}
 	fileBytes := make([]byte, h.Size)
@@ -135,11 +136,11 @@ func makeEntry(r *tar.Reader, h *tar.Header, path string) FileChangeInfo {
 		panic(err)
 	}
 	hash := md5.Sum(fileBytes)
-	return FileChangeInfo{
-		path:     path,
-		typeflag: h.Typeflag,
-		md5sum:   hash,
-		diffType: Unchanged,
+	return filetree.FileChangeInfo{
+		Path:     path,
+		Typeflag: h.Typeflag,
+		MD5sum:   hash,
+		DiffType: filetree.Unchanged,
 	}
 }
 
diff --git a/layerview.go b/layerview.go
deleted file mode 100644
index 1cbd437..0000000
--- a/layerview.go
+++ /dev/null
@@ -1,79 +0,0 @@
-package main
-
-import (
-	"fmt"
-
-	"github.com/jroimartin/gocui"
-)
-
-type LayerView struct {
-	name       string
-	gui        *gocui.Gui
-	view       *gocui.View
-	layerIndex uint
-	manifest   *Manifest
-}
-
-func NewLayerView(name string, gui *gocui.Gui, view *gocui.View, manifest *Manifest) (layerview *LayerView) {
-	layerview = new(LayerView)
-
-	// populate main fields
-	layerview.name = name
-	layerview.gui = gui
-	layerview.view = view
-	layerview.manifest = manifest
-
-	// set view options
-	layerview.view.Wrap = true
-	layerview.view.Highlight = true
-	layerview.view.SelBgColor = gocui.ColorGreen
-	layerview.view.SelFgColor = gocui.ColorBlack
-
-	layerview.render()
-
-	return layerview
-}
-
-func (view *LayerView) keybindings() error {
-
-	if err := view.gui.SetKeybinding("side", gocui.KeyArrowDown, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.cursorDown() }); err != nil {
-		return err
-	}
-	if err := view.gui.SetKeybinding("side", gocui.KeyArrowUp, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.cursorUp() }); err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func (view *LayerView) render() error {
-	view.gui.Update(func(g *gocui.Gui) error {
-		view.view.Clear()
-		for ix, layerName := range view.manifest.Layers {
-			fmt.Fprintf(view.view, "%d: %s\n", ix+1, layerName[0:25])
-		}
-		return nil
-	})
-	// todo: blerg
-	return nil
-}
-
-func (view *LayerView) cursorDown() error {
-	if int(view.layerIndex) < len(data.manifest.Layers) {
-		cursorDown(view.gui, view.view)
-		view.layerIndex++
-		view.render()
-		views.treeView.reset(StackRange(data.refTrees, view.layerIndex))
-	}
-	return nil
-}
-
-func (view *LayerView) cursorUp() error {
-	if int(view.layerIndex) > 0 {
-		cursorUp(view.gui, view.view)
-		view.layerIndex--
-		view.render()
-		views.treeView.reset(StackRange(data.refTrees, view.layerIndex))
-	}
-	return nil
-}
diff --git a/main.go b/main.go
deleted file mode 100644
index 6ea032c..0000000
--- a/main.go
+++ /dev/null
@@ -1,229 +0,0 @@
-package main
-
-import (
-	"bufio"
-	"encoding/json"
-	"fmt"
-	"io"
-	"os"
-
-	"log"
-
-	"github.com/docker/docker/client"
-	"github.com/jroimartin/gocui"
-	"golang.org/x/net/context"
-)
-
-var data struct {
-	refTrees []*FileTree
-	manifest *Manifest
-}
-
-var views struct {
-	treeView  *FileTreeView
-	layerView *LayerView
-}
-
-func check(e error) {
-	if e != nil {
-		panic(e)
-	}
-}
-
-func saveImage(readCloser io.ReadCloser) {
-	defer readCloser.Close()
-
-	path := "image"
-	if _, err := os.Stat(path); os.IsNotExist(err) {
-		os.Mkdir(path, 0755)
-	}
-
-	fo, err := os.Create("image/cache.tar")
-	check(err)
-
-	defer func() {
-		if err := fo.Close(); err != nil {
-			panic(err)
-		}
-	}()
-	w := bufio.NewWriter(fo)
-
-	buf := make([]byte, 1024)
-	for {
-		n, err := readCloser.Read(buf)
-		if err != nil && err != io.EOF {
-			panic(err)
-		}
-		if n == 0 {
-			break
-		}
-
-		if _, err := w.Write(buf[:n]); err != nil {
-			panic(err)
-		}
-	}
-
-	if err = w.Flush(); err != nil {
-		panic(err)
-	}
-}
-
-func demo() {
-	ctx := context.Background()
-	cli, err := client.NewEnvClient()
-	if err != nil {
-		panic(err)
-	}
-
-	// imageID := "golang:alpine"
-	imageID := "die-test:latest"
-
-	fmt.Println("Saving Image...")
-	readCloser, err := cli.ImageSave(ctx, []string{imageID})
-	check(err)
-	saveImage(readCloser)
-
-	for {
-		inspect, _, err := cli.ImageInspectWithRaw(ctx, imageID)
-		check(err)
-
-		history, err := cli.ImageHistory(ctx, imageID)
-		check(err)
-
-		historyStr, err := json.MarshalIndent(history, "", "  ")
-		check(err)
-
-		layerStr := ""
-		for idx, layer := range inspect.RootFS.Layers {
-			prefix := "├── "
-			if idx == len(inspect.RootFS.Layers)-1 {
-				prefix = "└── "
-			}
-			layerStr += fmt.Sprintf("%s%s\n", prefix, layer)
-		}
-
-		fmt.Printf("Image: %s\nId: %s\nParent: %s\nLayers: %d\n%sHistory: %s\n", imageID, inspect.ID, inspect.Parent, len(inspect.RootFS.Layers), layerStr, historyStr)
-
-		fmt.Println("\n")
-
-		if inspect.Parent == "" {
-			break
-		} else {
-			imageID = inspect.Parent
-		}
-	}
-	fmt.Println("See './image' for the cached image tar")
-}
-
-///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-func nextView(g *gocui.Gui, v *gocui.View) error {
-	if v == nil || v.Name() == views.layerView.name {
-		_, err := g.SetCurrentView(views.treeView.name)
-		return err
-	}
-	_, err := g.SetCurrentView(views.layerView.name)
-	return err
-}
-
-func cursorDown(g *gocui.Gui, v *gocui.View) error {
-	cx, cy := v.Cursor()
-
-	// if there isn't a next line
-	line, err := v.Line(cy + 1)
-	if err != nil {
-		// todo: handle error
-	}
-	if len(line) == 0 {
-		return nil
-	}
-	if err := v.SetCursor(cx, cy+1); err != nil {
-		ox, oy := v.Origin()
-		if err := v.SetOrigin(ox, oy+1); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-func cursorUp(g *gocui.Gui, v *gocui.View) error {
-	ox, oy := v.Origin()
-	cx, cy := v.Cursor()
-	if err := v.SetCursor(cx, cy-1); err != nil && oy > 0 {
-		if err := v.SetOrigin(ox, oy-1); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-func quit(g *gocui.Gui, v *gocui.View) error {
-	return gocui.ErrQuit
-}
-
-func keybindings(g *gocui.Gui) error {
-	if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
-		return err
-	}
-	//if err := g.SetKeybinding("main", gocui.MouseLeft, gocui.ModNone, toggleCollapse); err != nil {
-	//	return err
-	//}
-	if err := g.SetKeybinding("side", gocui.KeyCtrlSpace, gocui.ModNone, nextView); err != nil {
-		return err
-	}
-	if err := g.SetKeybinding("main", gocui.KeyCtrlSpace, gocui.ModNone, nextView); err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func layout(g *gocui.Gui) error {
-	maxX, maxY := g.Size()
-	splitCol := 50
-	if v, err := g.SetView("side", -1, -1, splitCol, maxY); err != nil {
-		if err != gocui.ErrUnknownView {
-			return err
-		}
-		views.layerView = NewLayerView("side", g, v, data.manifest)
-		views.layerView.keybindings()
-
-	}
-	if v, err := g.SetView("main", splitCol, -1, maxX, maxY); err != nil {
-		if err != gocui.ErrUnknownView {
-			return err
-		}
-
-		views.treeView = NewFileTreeView("main", g, v, StackRange(data.refTrees, 0))
-		views.treeView.keybindings()
-
-		if _, err := g.SetCurrentView(views.treeView.name); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-func main() {
-	demo()
-	initializeData()
-
-	g, err := gocui.NewGui(gocui.OutputNormal)
-	if err != nil {
-		log.Panicln(err)
-	}
-	defer g.Close()
-
-	g.Cursor = false
-	//g.Mouse = true
-	g.SetManagerFunc(layout)
-
-	if err := keybindings(g); err != nil {
-		log.Panicln(err)
-	}
-
-	if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
-		log.Panicln(err)
-	}
-
-}
diff --git a/ui/filetreeview.go b/ui/filetreeview.go
new file mode 100644
index 0000000..138c796
--- /dev/null
+++ b/ui/filetreeview.go
@@ -0,0 +1,121 @@
+package ui
+
+import (
+	"fmt"
+
+	"github.com/jroimartin/gocui"
+	"github.com/wagoodman/docker-image-explorer/filetree"
+)
+
+
+type FileTreeView struct {
+	Name         string
+	gui          *gocui.Gui
+	view         *gocui.View
+	TreeIndex uint
+	Tree         *filetree.FileTree
+	RefTrees []*filetree.FileTree
+}
+
+func NewFileTreeView(name string, gui *gocui.Gui, tree *filetree.FileTree, refTrees []*filetree.FileTree) (treeview *FileTreeView) {
+	treeview = new(FileTreeView)
+
+	// populate main fields
+	treeview.Name = name
+	treeview.gui = gui
+	treeview.Tree = tree
+	treeview.RefTrees = refTrees
+
+	return treeview
+}
+
+func (view *FileTreeView) Setup(v *gocui.View) error {
+
+	// set view options
+	view.view = v
+	view.view.Editable = false
+	view.view.Wrap = false
+	view.view.Highlight = true
+	view.view.SelBgColor = gocui.ColorGreen
+	view.view.SelFgColor = gocui.ColorBlack
+
+	// set keybindings
+	if err := view.gui.SetKeybinding(view.Name, gocui.KeyArrowDown, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.CursorDown() }); err != nil {
+		return err
+	}
+	if err := view.gui.SetKeybinding(view.Name, gocui.KeyArrowUp, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.CursorUp() }); err != nil {
+		return err
+	}
+	if err := view.gui.SetKeybinding(view.Name, gocui.KeySpace, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.toggleCollapse() }); err != nil {
+		return err
+	}
+
+	view.Render()
+
+	return nil
+}
+
+// Mehh, this is just a bad method
+func (view *FileTreeView) reset(tree *filetree.FileTree) error {
+	view.Tree = tree
+	view.view.SetCursor(0, 0)
+	view.TreeIndex = 0
+	return view.Render()
+}
+
+func (view *FileTreeView) CursorDown() error {
+	err := CursorDown(view.gui, view.view)
+	if err == nil {
+		view.TreeIndex++
+	}
+	return nil
+}
+
+func (view *FileTreeView) CursorUp() error {
+	err := CursorUp(view.gui, view.view)
+	if err == nil {
+		view.TreeIndex--
+	}
+	return nil
+}
+
+func (view *FileTreeView) getAbsPositionNode() (node *filetree.FileNode) {
+	var visiter func(*filetree.FileNode) error
+	var evaluator func(*filetree.FileNode) bool
+	var dfsCounter uint
+
+	visiter = func(curNode *filetree.FileNode) error {
+		if dfsCounter == view.TreeIndex {
+			node = curNode
+		}
+		dfsCounter++
+		return nil
+	}
+
+	evaluator = func(curNode *filetree.FileNode) bool {
+		return !curNode.Collapsed
+	}
+
+	err := view.Tree.VisitDepthParentFirst(visiter, evaluator)
+	if err != nil {
+		// todo: you guessed it, check errors
+	}
+
+	return node
+}
+
+func (view *FileTreeView) toggleCollapse() error {
+	node := view.getAbsPositionNode()
+	node.Collapsed = !node.Collapsed
+	return view.Render()
+}
+
+func (view *FileTreeView) Render() error {
+	renderString := view.Tree.String()
+	view.gui.Update(func(g *gocui.Gui) error {
+		view.view.Clear()
+		_, err := fmt.Fprintln(view.view, renderString)
+		return err
+	})
+	return nil
+}
diff --git a/ui/layerview.go b/ui/layerview.go
new file mode 100644
index 0000000..281e20d
--- /dev/null
+++ b/ui/layerview.go
@@ -0,0 +1,85 @@
+package ui
+
+import (
+	"fmt"
+
+	"github.com/jroimartin/gocui"
+	"github.com/wagoodman/docker-image-explorer/image"
+	"github.com/wagoodman/docker-image-explorer/filetree"
+)
+
+
+type LayerView struct {
+	Name       string
+	gui        *gocui.Gui
+	view       *gocui.View
+	LayerIndex uint
+	Manifest   *image.Manifest
+}
+
+func NewLayerView(name string, gui *gocui.Gui, manifest *image.Manifest) (layerview *LayerView) {
+	layerview = new(LayerView)
+
+	// populate main fields
+	layerview.Name = name
+	layerview.gui = gui
+	layerview.Manifest = manifest
+
+	return layerview
+}
+
+func (view *LayerView) Setup(v *gocui.View) error {
+
+	// set view options
+	view.view = v
+	view.view.Wrap = true
+	view.view.Highlight = true
+	view.view.SelBgColor = gocui.ColorGreen
+	view.view.SelFgColor = gocui.ColorBlack
+
+	// set keybindings
+	if err := view.gui.SetKeybinding("side", gocui.KeyArrowDown, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.CursorDown() }); err != nil {
+		return err
+	}
+	if err := view.gui.SetKeybinding("side", gocui.KeyArrowUp, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.CursorUp() }); err != nil {
+		return err
+	}
+
+	view.Render()
+
+	return nil
+}
+
+func (view *LayerView) Render() error {
+	view.gui.Update(func(g *gocui.Gui) error {
+		view.view.Clear()
+		for ix, layerName := range view.Manifest.Layers {
+			fmt.Fprintf(view.view, "%d: %s\n", ix+1, layerName[0:25])
+		}
+		return nil
+	})
+	// todo: blerg
+	return nil
+}
+
+func (view *LayerView) CursorDown() error {
+	if int(view.LayerIndex) < len(view.Manifest.Layers) {
+		CursorDown(view.gui, view.view)
+		view.LayerIndex++
+		view.Render()
+		// this line is evil
+		Views.Tree.reset(filetree.StackRange(Views.Tree.RefTrees, view.LayerIndex))
+	}
+	return nil
+}
+
+func (view *LayerView) CursorUp() error {
+	if int(view.LayerIndex) > 0 {
+		CursorUp(view.gui, view.view)
+		view.LayerIndex--
+		view.Render()
+		// this line is evil
+		Views.Tree.reset(filetree.StackRange(Views.Tree.RefTrees, view.LayerIndex))
+	}
+	return nil
+}
diff --git a/ui/ui.go b/ui/ui.go
new file mode 100644
index 0000000..48321ce
--- /dev/null
+++ b/ui/ui.go
@@ -0,0 +1,122 @@
+package ui
+
+import (
+	"github.com/jroimartin/gocui"
+	"github.com/wagoodman/docker-image-explorer/filetree"
+	"log"
+	"github.com/wagoodman/docker-image-explorer/image"
+)
+
+var Views struct {
+	Tree  *FileTreeView
+	Layer *LayerView
+}
+
+func nextView(g *gocui.Gui, v *gocui.View) error {
+	if v == nil || v.Name() == Views.Layer.Name {
+		_, err := g.SetCurrentView(Views.Tree.Name)
+		return err
+	}
+	_, err := g.SetCurrentView(Views.Layer.Name)
+	return err
+}
+
+func CursorDown(g *gocui.Gui, v *gocui.View) error {
+	cx, cy := v.Cursor()
+
+	// if there isn't a next line
+	line, err := v.Line(cy + 1)
+	if err != nil {
+		// todo: handle error
+	}
+	if len(line) == 0 {
+		return nil
+	}
+	if err := v.SetCursor(cx, cy+1); err != nil {
+		ox, oy := v.Origin()
+		if err := v.SetOrigin(ox, oy+1); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func CursorUp(g *gocui.Gui, v *gocui.View) error {
+	ox, oy := v.Origin()
+	cx, cy := v.Cursor()
+	if err := v.SetCursor(cx, cy-1); err != nil && oy > 0 {
+		if err := v.SetOrigin(ox, oy-1); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func quit(g *gocui.Gui, v *gocui.View) error {
+	return gocui.ErrQuit
+}
+
+func keybindings(g *gocui.Gui) error {
+	if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
+		return err
+	}
+	//if err := g.SetKeybinding("main", gocui.MouseLeft, gocui.ModNone, toggleCollapse); err != nil {
+	//	return err
+	//}
+	if err := g.SetKeybinding("side", gocui.KeyCtrlSpace, gocui.ModNone, nextView); err != nil {
+		return err
+	}
+	if err := g.SetKeybinding("main", gocui.KeyCtrlSpace, gocui.ModNone, nextView); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func layout(g *gocui.Gui) error {
+	maxX, maxY := g.Size()
+	splitCol := 50
+	if view, err := g.SetView("side", -1, -1, splitCol, maxY); err != nil {
+		if err != gocui.ErrUnknownView {
+			return err
+		}
+		Views.Layer.Setup(view)
+
+	}
+	if view, err := g.SetView("main", splitCol, -1, maxX, maxY); err != nil {
+		if err != gocui.ErrUnknownView {
+			return err
+		}
+
+		Views.Tree.Setup(view)
+
+		if _, err := g.SetCurrentView(Views.Tree.Name); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func Run(manifest *image.Manifest, refTrees []*filetree.FileTree) {
+
+	g, err := gocui.NewGui(gocui.OutputNormal)
+	if err != nil {
+		log.Panicln(err)
+	}
+	defer g.Close()
+
+	Views.Layer = NewLayerView("side", g, manifest)
+	Views.Tree = NewFileTreeView("main", g, filetree.StackRange(refTrees, 0), refTrees)
+
+	g.Cursor = false
+	//g.Mouse = true
+	g.SetManagerFunc(layout)
+
+	if err := keybindings(g); err != nil {
+		log.Panicln(err)
+	}
+
+	if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
+		log.Panicln(err)
+	}
+}
\ No newline at end of file
diff --git a/ui/view.go b/ui/view.go
new file mode 100644
index 0000000..bac308c
--- /dev/null
+++ b/ui/view.go
@@ -0,0 +1,10 @@
+package ui
+
+import 	"github.com/jroimartin/gocui"
+
+type View interface {
+	Setup(*gocui.View) error
+	CursorDown() error
+	CursorUp() error
+	Render() error
+}
diff --git a/view.go b/view.go
deleted file mode 100644
index 6334732..0000000
--- a/view.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package main
-
-type View interface {
-	keybindings() error
-	cursorDown() error
-	cursorUp() error
-	render() error
-}