diff --git a/cmd/ipfs2/ipfs.go b/cmd/ipfs2/ipfs.go
index bb328085ae89e170bb3ae2d32b0e1b5f40ff29ba..ef359e370fda31ff891b53c9ffa28ccb354ec0d6 100644
--- a/cmd/ipfs2/ipfs.go
+++ b/cmd/ipfs2/ipfs.go
@@ -9,7 +9,8 @@ var Root = &cmds.Command{
 	Options: commands.Root.Options,
 	Help:    commands.Root.Help,
 	Subcommands: map[string]*cmds.Command{
-		"daemon": daemonCmd,
-		"init":   initCmd,
+		"daemon": daemonCmd, // TODO name
+		"init":   initCmd,   // TODO name
+		"tour":   cmdTour,
 	},
 }
diff --git a/cmd/ipfs2/tour.go b/cmd/ipfs2/tour.go
new file mode 100644
index 0000000000000000000000000000000000000000..b604e843e73a9a6ea7c2b6495052463018ccd601
--- /dev/null
+++ b/cmd/ipfs2/tour.go
@@ -0,0 +1,151 @@
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"os"
+
+	cmds "github.com/jbenet/go-ipfs/commands"
+	"github.com/jbenet/go-ipfs/config"
+	"github.com/jbenet/go-ipfs/core/commands2/internal"
+	"github.com/jbenet/go-ipfs/tour"
+)
+
+var cmdTour = &cmds.Command{
+
+	Arguments: []cmds.Argument{
+		cmds.Argument{"number", cmds.ArgString, false, true},
+	},
+
+	// TODO UsageLine: "tour [<number>]",
+	// TODO Short:     "Take the IPFS Tour.",
+
+	Help: `ipfs tour - Take the IPFS Tour.
+
+    ipfs tour [<number>]   - Show tour topic. Default to current.
+    ipfs tour next         - Show the next tour topic.
+    ipfs tour list         - Show a list of topics.
+    ipfs tour restart      - Restart the tour.
+
+This is a tour that takes you through various IPFS concepts,
+features, and tools to make sure you get up to speed with
+IPFS very quickly. To start, run:
+
+    ipfs tour
+`,
+	Subcommands: map[string]*cmds.Command{
+		"list":    cmdIpfsTourList,
+		"next":    cmdIpfsTourNext,
+		"restart": cmdIpfsTourRestart,
+	},
+	Run: func(res cmds.Response, req cmds.Request) {
+
+		out := new(bytes.Buffer)
+		cfg := req.Context().Config
+		strs, err := internal.ToStrings(req.Arguments())
+		if err != nil {
+			res.SetError(err, cmds.ErrNormal)
+			return
+		}
+
+		topic := tour.TopicID(cfg.Tour.Last)
+		if len(strs) > 0 {
+			topic = tour.TopicID(strs[0])
+		}
+
+		err = tourShow(out, topic)
+		if err != nil {
+			res.SetError(err, cmds.ErrNormal)
+			return
+		}
+
+		res.SetOutput(out)
+	},
+}
+
+var cmdIpfsTourNext = &cmds.Command{
+	Help: "Show the next IPFS Tour topic.",
+	Run: func(res cmds.Response, req cmds.Request) {
+		var w bytes.Buffer
+		cfg := req.Context().Config
+		path := req.Context().ConfigRoot
+
+		topic := tour.NextTopic(tour.TopicID(cfg.Tour.Last))
+		if err := tourShow(&w, topic); err != nil {
+			res.SetError(err, cmds.ErrNormal)
+			return
+		}
+
+		// topic changed, not last. write it out.
+		if string(topic) != cfg.Tour.Last {
+			cfg.Tour.Last = string(topic)
+			err := writeConfig(path, cfg)
+			if err != nil {
+				res.SetError(err, cmds.ErrNormal)
+				return
+			}
+		}
+
+		w.WriteTo(os.Stdout) // TODO write to res.SetValue
+	},
+}
+
+var cmdIpfsTourRestart = &cmds.Command{
+	Help: "Restart the IPFS Tour.",
+	Run: func(res cmds.Response, req cmds.Request) {
+		path := req.Context().ConfigRoot
+		cfg := req.Context().Config
+
+		cfg.Tour.Last = ""
+		err := writeConfig(path, cfg)
+		if err != nil {
+			res.SetError(err, cmds.ErrNormal)
+		}
+	},
+}
+
+var cmdIpfsTourList = &cmds.Command{
+	Help: "Show a list of IPFS Tour topics.",
+	Run: func(res cmds.Response, req cmds.Request) {
+		var w bytes.Buffer
+		tourListCmd(&w, req.Context().Config)
+		w.WriteTo(os.Stdout) // TODO use res.SetOutput(output)
+	},
+}
+
+func tourListCmd(w io.Writer, cfg *config.Config) {
+
+	lastid := tour.TopicID(cfg.Tour.Last)
+	for _, id := range tour.IDs {
+		c := ' '
+		switch {
+		case id == lastid:
+			c = '*'
+		case id.LessThan(lastid):
+			c = '✓'
+		}
+
+		t := tour.Topics[id]
+		fmt.Fprintf(w, "- %c %-5.5s %s\n", c, id, t.Title)
+	}
+}
+
+func tourShow(w io.Writer, id tour.ID) error {
+	t, found := tour.Topics[id]
+	if !found {
+		return fmt.Errorf("no topic with id: %s", id)
+	}
+
+	fmt.Fprintf(w, "Tour %s - %s\n\n%s\n", t.ID, t.Title, t.Text)
+	return nil
+}
+
+// TODO share func
+func writeConfig(path string, cfg *config.Config) error {
+	filename, err := config.Filename(path)
+	if err != nil {
+		return err
+	}
+	return config.WriteConfigFile(filename, cfg)
+}