diff --git a/lib/Proteome/Add.hs b/lib/Proteome/Add.hs
--- a/lib/Proteome/Add.hs
+++ b/lib/Proteome/Add.hs
@@ -1,19 +1,41 @@
 module Proteome.Add(
   proAdd,
+  proAddCmd,
 )
 where
 
 import Control.Lens (over)
+import Data.List.Utils (split)
+import Neovim (nvim_err_writeln', CommandArguments)
 import qualified Ribosome.Data.Ribo as Ribo (modify)
 import Proteome.Data.AddOptions (AddOptions(AddOptions))
 import Proteome.Data.Env (_projects)
-import Proteome.Data.Project (meta, ProjectMetadata(VirtualProject))
+import Proteome.Data.Project (
+  meta,
+  ProjectMetadata(VirtualProject),
+  ProjectName(ProjectName),
+  ProjectType(ProjectType),
+  )
 import Proteome.Project.Resolve (resolveProjectFromConfig)
 import Proteome.Data.Proteome (Proteome)
 
-proAdd :: AddOptions -> Proteome ()
-proAdd (AddOptions name tpe) = do
-  project <- resolveProjectFromConfig Nothing name (Just tpe)
+add :: ProjectName -> Maybe ProjectType -> Proteome ()
+add name tpe = do
+  project <- resolveProjectFromConfig Nothing name tpe
   case meta project of
     VirtualProject _ -> return ()
     _ -> Ribo.modify $ over _projects (\p -> p ++ [project])
+
+proAdd :: AddOptions -> Proteome ()
+proAdd (AddOptions name tpe) =
+  add name (Just tpe)
+
+addFromName :: ProjectName -> Proteome ()
+addFromName name = add name Nothing
+
+proAddCmd :: CommandArguments -> String -> Proteome ()
+proAddCmd _ spec =
+  case split "/" spec of
+    [tpe, name] -> add (ProjectName name) (Just (ProjectType tpe))
+    [name] -> addFromName (ProjectName name)
+    _ -> nvim_err_writeln' $ "invalid project spec: " ++ spec
diff --git a/lib/Proteome/Data/ActiveProject.hs b/lib/Proteome/Data/ActiveProject.hs
new file mode 100644
--- /dev/null
+++ b/lib/Proteome/Data/ActiveProject.hs
@@ -0,0 +1,35 @@
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Proteome.Data.ActiveProject(
+  ActiveProject(ActiveProject),
+) where
+
+import GHC.Generics (Generic)
+import Control.DeepSeq (NFData)
+import qualified Data.Map as Map (fromList)
+import Neovim (Object)
+import Neovim.Classes (NvimObject(..), Dictionary)
+import Proteome.Data.Project(
+  ProjectName,
+  ProjectType
+  )
+
+data ActiveProject =
+  ActiveProject {
+    name :: ProjectName,
+    tpe :: ProjectType
+  }
+  deriving (Eq, Show, Generic, NFData)
+
+instance NvimObject ActiveProject where
+  toObject ActiveProject {..} =
+    (toObject :: Dictionary -> Object) . Map.fromList $
+    [
+      ("name", toObject name),
+      ("tpe", toObject tpe)
+    ]
+  fromObject _ = Left "ActiveProject is write-only"
diff --git a/lib/Proteome/Data/Env.hs b/lib/Proteome/Data/Env.hs
--- a/lib/Proteome/Data/Env.hs
+++ b/lib/Proteome/Data/Env.hs
@@ -5,6 +5,7 @@
   _mainProject,
   _projects,
   _errors,
+  _currentProjectIndex,
 ) where
 
 import Control.Lens (makeClassy_)
@@ -16,11 +17,12 @@
   Env {
     mainProject :: Project,
     projects :: [Project],
-    errors :: Errors
+    errors :: Errors,
+    currentProjectIndex :: Int
   }
   deriving Show
 
 makeClassy_ ''Env
 
 instance Default Env where
-  def = Env def def def
+  def = Env def def def def
diff --git a/lib/Proteome/Data/Project.hs b/lib/Proteome/Data/Project.hs
--- a/lib/Proteome/Data/Project.hs
+++ b/lib/Proteome/Data/Project.hs
@@ -16,7 +16,7 @@
 
 import GHC.Generics (Generic)
 import Control.Lens (makeClassy_)
-import Control.DeepSeq
+import Control.DeepSeq (NFData)
 import Data.Default.Class (Default(def))
 import Neovim.Classes (NvimObject(..))
 import Ribosome.Internal.NvimObject (deriveString)
diff --git a/lib/Proteome/Init.hs b/lib/Proteome/Init.hs
--- a/lib/Proteome/Init.hs
+++ b/lib/Proteome/Init.hs
@@ -4,81 +4,84 @@
   proteomeStage4,
   proteomePoll,
   proteomeStage1,
+  resolveMainProject,
+  initWithMain,
+  resolveAndInitMain,
 ) where
 
 import Data.Default.Class (Default(def))
-import System.Directory (getCurrentDirectory, makeAbsolute)
-import System.FilePath (takeFileName, takeDirectory)
+import qualified Control.Lens as Lens (set)
+import Data.Maybe (fromMaybe)
+import Data.Either.Combinators (rightToMaybe)
 import System.Log.Logger (updateGlobalLogger, setLevel, Priority(ERROR))
-import Control.Monad.IO.Class (MonadIO, liftIO)
+import Control.Monad.IO.Class (liftIO)
+import UnliftIO.STM (TVar)
 import Neovim (Neovim)
+import Neovim.Context.Internal (Config(customConfig), asks')
 import Ribosome.Config.Setting (settingE, updateSetting)
 import Ribosome.Data.Ribo (Ribo)
-import qualified Ribosome.Data.Ribo as Ribo (inspect)
-import Ribosome.Data.Ribosome (Ribosome(Ribosome), newInternalTVar)
+import qualified Ribosome.Data.Ribo as Ribo (inspect, modify)
+import Ribosome.Data.Ribosome (newRibosome, Ribosome)
 import Ribosome.Internal.IO (retypeNeovim)
-import Proteome.Data.Env (Env(Env))
-import qualified Proteome.Data.Env as Env (mainProject)
-import Proteome.Data.Proteome
+import qualified Proteome.Data.Env as Env (mainProject, _mainProject)
+import Proteome.Data.Env (Env)
+import Proteome.Data.Proteome (Proteome)
 import Proteome.Data.Project (
   Project(meta),
-  ProjectName(..),
-  ProjectRoot(..),
   ProjectType(..),
-  ProjectMetadata(DirProject),
+  ProjectMetadata(DirProject, VirtualProject),
   )
+import Proteome.Project (pathData)
 import Proteome.Project.Resolve (resolveProjectFromConfig)
+import Proteome.Project.Activate (activateProject)
 import Proteome.Config (readConfig)
 import Proteome.PersistBuffers (loadBuffers)
-import Proteome.Log
+import qualified Proteome.Log as Log
 import qualified Proteome.Settings as S
 
-pathData :: MonadIO m => Either String FilePath -> m (ProjectRoot, ProjectName, ProjectType)
-pathData override = do
-  cwd <- liftIO $ either (const getCurrentDirectory) pure override
-  absMainDir <- liftIO $ makeAbsolute cwd
-  return (
-    ProjectRoot absMainDir,
-    ProjectName $ takeFileName absMainDir,
-    ProjectType $ (takeFileName . takeDirectory) absMainDir
-    )
-
-mainProject :: Ribo e Project
-mainProject = do
+resolveMainProject :: Ribo e Project
+resolveMainProject = do
   mainDir <- settingE S.mainProjectDir
-  (root, name, tpe) <- pathData mainDir
+  (root, name, tpe) <- pathData (rightToMaybe mainDir)
   resolveProjectFromConfig (Just root) name (Just tpe)
 
-loadPersistedBuffers :: Project -> Neovim e ()
-loadPersistedBuffers _ = return ()
+setMainProject :: Project -> Proteome ()
+setMainProject project =
+  Ribo.modify $ Lens.set Env._mainProject project
 
-updateMainType :: Maybe ProjectType -> Ribo e ()
-updateMainType (Just tpe) = updateSetting S.mainType tpe
-updateMainType Nothing = return ()
+updateMainType :: Maybe ProjectType -> Proteome ()
+updateMainType tpe = updateSetting S.mainType (fromMaybe (ProjectType "none") tpe)
 
-setProjectVars :: ProjectMetadata -> Ribo e ()
-setProjectVars (DirProject name _ tpe) = do
+setMainProjectVars :: ProjectMetadata -> Proteome ()
+setMainProjectVars (DirProject name _ tpe) = do
   updateSetting S.mainName name
   updateMainType tpe
-setProjectVars _ = return ()
+setMainProjectVars (VirtualProject name) = do
+  updateSetting S.mainName name
+  updateMainType (Just (ProjectType "virtual"))
 
-initWithMain :: Project -> Ribo e Env
+initWithMain :: Project -> Proteome ()
 initWithMain main = do
-  debugS $ "initializing with main project: " ++ show main
-  loadPersistedBuffers main
-  setProjectVars (meta main)
-  return $ Env main [] def
+  Log.debugS $ "initializing with main project: " ++ show main
+  setMainProject main
+  setMainProjectVars (meta main)
+  activateProject main
 
-initialize' :: Ribo e Env
-initialize' = do
-  main <- mainProject
+resolveAndInitMain :: Proteome ()
+resolveAndInitMain = do
+  main <- resolveMainProject
   initWithMain main
 
-initialize :: Neovim e Env
+initialize' :: Proteome (Ribosome (TVar Env))
+initialize' = do
+  resolveAndInitMain
+  asks' customConfig
+
+initialize :: Neovim e (Ribosome (TVar Env))
 initialize = do
   liftIO $ updateGlobalLogger "Neovim.Plugin" (setLevel ERROR)
-  internal <- newInternalTVar
-  retypeNeovim (Ribosome "proteome" internal) initialize'
+  ribo <- newRibosome "proteome" def
+  retypeNeovim (const ribo) initialize'
 
 proteomeStage1 :: Proteome ()
 proteomeStage1 = loadBuffers
diff --git a/lib/Proteome/Log.hs b/lib/Proteome/Log.hs
--- a/lib/Proteome/Log.hs
+++ b/lib/Proteome/Log.hs
@@ -3,10 +3,11 @@
   info,
   debugS,
   infoS,
+  R.p,
 ) where
 
 import Ribosome.Data.Ribo (Ribo)
-import qualified Ribosome.Log as R (debug, info)
+import qualified Ribosome.Log as R (debug, info, p)
 
 debug :: String -> Ribo e ()
 debug = R.debug "proteome"
diff --git a/lib/Proteome/Plugin.hs b/lib/Proteome/Plugin.hs
--- a/lib/Proteome/Plugin.hs
+++ b/lib/Proteome/Plugin.hs
@@ -11,6 +11,7 @@
 import Neovim (
   Plugin(..),
   function',
+  command,
   autocmd,
   Neovim,
   StartupConfig,
@@ -18,12 +19,12 @@
   NeovimPlugin,
   Synchronous(..),
   wrapPlugin,
-  acmdGroup
+  CommandOption(CmdBang),
   )
-import Ribosome.Data.Ribosome (Ribosome, newRibosome)
+import Ribosome.Data.Ribosome (Ribosome)
 import Proteome.Init (proteomePoll, proteomeStage1, proteomeStage2, proteomeStage4, initialize)
 import Proteome.Data.Env (Env)
-import Proteome.Add (proAdd)
+import Proteome.Add (proAdd, proAddCmd)
 import Proteome.Config (proReadConfig)
 import Proteome.Tags (proTags)
 import Proteome.Save (proSave)
@@ -39,15 +40,15 @@
       $(function' 'proteomeStage2) Async,
       $(function' 'proteomeStage4) Async,
       $(function' 'proAdd) Async,
+      $(command "ProAdd" 'proAddCmd) [CmdBang],
       $(function' 'proSave) Async,
       $(function' 'proTags) Async,
       $(function' 'proReadConfig) Sync,
-      $(autocmd 'bufEnter) "BufEnter" def { acmdGroup = Just "proteome" }
+      $(autocmd 'bufEnter) "BufEnter" def
     ]
   }
 
 plugin :: Neovim (StartupConfig NeovimConfig) NeovimPlugin
 plugin = do
   env <- initialize
-  env' <- newRibosome "proteome" env
-  wrapPlugin $ plugin' env'
+  wrapPlugin $ plugin' env
diff --git a/lib/Proteome/Project.hs b/lib/Proteome/Project.hs
--- a/lib/Proteome/Project.hs
+++ b/lib/Proteome/Project.hs
@@ -1,14 +1,41 @@
 module Proteome.Project(
   allProjects,
+  currentProject,
+  pathData,
 ) where
 
+import Control.Monad.IO.Class (MonadIO, liftIO)
+import Safe (atMay)
+import System.Directory (getCurrentDirectory, makeAbsolute)
+import System.FilePath (takeFileName, takeDirectory)
 import qualified Ribosome.Data.Ribo as Ribo (inspect)
 import Proteome.Data.Proteome (Proteome)
-import Proteome.Data.Project (Project)
-import Proteome.Data.Env (Env(mainProject, projects))
+import Proteome.Data.Project (
+  Project,
+  ProjectName(..),
+  ProjectRoot(..),
+  ProjectType(..),
+  )
+import Proteome.Data.Env (Env(mainProject, projects, currentProjectIndex))
 
 allProjects :: Proteome [Project]
 allProjects = do
   main <- Ribo.inspect mainProject
   extra <- Ribo.inspect projects
   return $ main : extra
+
+currentProject :: Proteome (Maybe Project)
+currentProject = do
+  index <- Ribo.inspect currentProjectIndex
+  pros <- allProjects
+  return $ atMay pros index
+
+pathData :: MonadIO m => Maybe FilePath -> m (ProjectRoot, ProjectName, ProjectType)
+pathData override = do
+  cwd <- liftIO $ maybe getCurrentDirectory pure override
+  absMainDir <- liftIO $ makeAbsolute cwd
+  return (
+    ProjectRoot absMainDir,
+    ProjectName $ takeFileName absMainDir,
+    ProjectType $ (takeFileName . takeDirectory) absMainDir
+    )
diff --git a/lib/Proteome/Project/Activate.hs b/lib/Proteome/Project/Activate.hs
new file mode 100644
--- /dev/null
+++ b/lib/Proteome/Project/Activate.hs
@@ -0,0 +1,65 @@
+module Proteome.Project.Activate(
+  activateProject,
+  activateCurrentProject,
+  proPrev,
+  proNext,
+) where
+
+import Control.Lens (over)
+import Control.Monad (when)
+import Control.Monad.IO.Class (liftIO)
+import Data.Maybe (fromMaybe)
+import System.Directory (doesDirectoryExist)
+import Neovim (vim_command')
+import Ribosome.Config.Setting (updateSetting)
+import Ribosome.Data.Ribo (Ribo)
+import qualified Ribosome.Data.Ribo as Ribo (modify)
+import Proteome.Data.Project (
+  Project(Project, meta),
+  ProjectMetadata(DirProject, VirtualProject),
+  ProjectType(ProjectType),
+  ProjectRoot(ProjectRoot),
+  )
+import Proteome.Data.ActiveProject (ActiveProject(ActiveProject))
+import Proteome.Data.Proteome (Proteome)
+import Proteome.Project (currentProject, allProjects)
+import qualified Proteome.Data.Env as Env (_currentProjectIndex)
+import qualified Proteome.Settings as S (active)
+
+activeProject :: Project -> ActiveProject
+activeProject (Project (DirProject name _ tpe) _ _ _) = ActiveProject name (fromMaybe (ProjectType "none") tpe)
+activeProject (Project (VirtualProject name) _ _ _) = ActiveProject name (ProjectType "virtual")
+
+activateDirProject :: ProjectMetadata -> Ribo e ()
+activateDirProject (DirProject _ (ProjectRoot root) _) = do
+  exists <- liftIO $ doesDirectoryExist root
+  when exists $ vim_command' $ "chdir " ++ root
+activateDirProject _ = return ()
+
+activateProject :: Project -> Ribo e ()
+activateProject project = do
+  updateSetting S.active $ activeProject project
+  activateDirProject (meta project)
+
+activateCurrentProject :: Proteome ()
+activateCurrentProject = do
+  pro <- currentProject
+  mapM_ activateProject pro
+
+cycleProjectIndex :: (Int -> Int) -> Proteome ()
+cycleProjectIndex f = do
+  pros <- allProjects
+  let
+    trans:: Int -> Int
+    trans a = f a `rem` length pros
+  Ribo.modify $ over Env._currentProjectIndex trans
+
+proPrev :: Proteome ()
+proPrev = do
+  cycleProjectIndex (subtract 1)
+  activateCurrentProject
+
+proNext :: Proteome ()
+proNext = do
+  cycleProjectIndex (+1)
+  activateCurrentProject
diff --git a/lib/Proteome/Project/Resolve.hs b/lib/Proteome/Project/Resolve.hs
--- a/lib/Proteome/Project/Resolve.hs
+++ b/lib/Proteome/Project/Resolve.hs
@@ -12,13 +12,16 @@
 import Data.List.Utils (uniq)
 import Data.Maybe (fromMaybe)
 import Data.Map.Strict ((!?), Map)
+import Safe (headMay)
 import System.Directory (doesDirectoryExist)
 import System.FilePath (takeDirectory, (</>))
+import System.Path.Glob (glob)
 import Ribosome.File (canonicalPaths)
-import Proteome.Config (ProjectConfig(ProjectConfig))
 import Ribosome.Config.Setting (setting)
+import Ribosome.Data.Foldable (findMapMaybeM)
 import Ribosome.Data.Maybe (orElse)
 import Ribosome.Data.Ribo (Ribo)
+import Proteome.Config (ProjectConfig(ProjectConfig))
 import Proteome.Data.Project (
   Project(Project),
   ProjectName(..),
@@ -29,6 +32,7 @@
   )
 import Proteome.Data.ProjectSpec (ProjectSpec(ProjectSpec))
 import qualified Proteome.Data.ProjectSpec as PS (ProjectSpec(..))
+import Proteome.Project (pathData)
 import qualified Proteome.Settings as S
 
 projectFromSegments :: ProjectType -> ProjectName -> ProjectRoot -> Project
@@ -81,6 +85,20 @@
     byTypeName = byProjectTypeName explicit name tpe
     byPath = root >>= resolveByTypeAndPath baseDirs name tpe
 
+fromProjectRoot :: FilePath -> IO Project
+fromProjectRoot dir = do
+  (root, name, tpe) <- pathData (Just dir)
+  return $ projectFromSegments tpe name root
+
+projectFromNameIn :: ProjectName -> FilePath -> IO (Maybe Project)
+projectFromNameIn (ProjectName name) base = do
+  candidates <- glob $ base ++ "/*/" ++ name
+  mapM fromProjectRoot (headMay candidates)
+
+resolveByName :: [FilePath] -> ProjectName -> IO (Maybe Project)
+resolveByName baseDirs name =
+  findMapMaybeM (projectFromNameIn name) baseDirs
+
 resolveByRoot :: [ProjectSpec] -> ProjectRoot -> Maybe Project
 resolveByRoot explicit root =
   fmap projectFromSpec byRoot
@@ -116,9 +134,11 @@
   IO Project
 resolveProject baseDirs explicit config root name tpe = do
   byType <- traverse (resolveByType baseDirs explicit root name) tpe
-  let byTypeOrVirtual = fromMaybe (virtualProject name) (join byType)
+  byName <- resolveByName baseDirs name
+  let byNameOrVirtual = fromMaybe (virtualProject name) byName
+  let byTypeOrName = fromMaybe byNameOrVirtual (join byType)
   let byRoot = root >>= resolveByRoot explicit
-  let project = fromMaybe byTypeOrVirtual byRoot
+  let project = fromMaybe byTypeOrName byRoot
   return $ augmentFromConfig config project
 
 resolveProjectFromConfig :: Maybe ProjectRoot -> ProjectName -> Maybe ProjectType -> Ribo e Project
diff --git a/lib/Proteome/Settings.hs b/lib/Proteome/Settings.hs
--- a/lib/Proteome/Settings.hs
+++ b/lib/Proteome/Settings.hs
@@ -10,11 +10,13 @@
   tagsArgs,
   tagsFork,
   tagsFileName,
+  active,
 ) where
 
 import Data.Default.Class (Default(def))
 import Ribosome.Config.Setting
 import Proteome.Data.Project (ProjectName, ProjectType)
+import Proteome.Data.ActiveProject (ActiveProject)
 import Proteome.Data.ProjectSpec (ProjectSpec)
 import Proteome.Config
 
@@ -38,6 +40,9 @@
 
 mainType :: Setting ProjectType
 mainType = Setting "main_type" True Nothing
+
+active :: Setting ActiveProject
+active = Setting "active" True Nothing
 
 tagsCommand :: Setting String
 tagsCommand = Setting "tags_command" True (Just "ctags")
diff --git a/lib/Ribosome/Api/Path.hs b/lib/Ribosome/Api/Path.hs
new file mode 100644
--- /dev/null
+++ b/lib/Ribosome/Api/Path.hs
@@ -0,0 +1,8 @@
+module Ribosome.Api.Path(
+  nvimCwd,
+) where
+
+import Neovim (Neovim, vim_call_function', fromObject')
+
+nvimCwd :: Neovim e FilePath
+nvimCwd = vim_call_function' "getcwd" [] >>= fromObject'
diff --git a/lib/Ribosome/Data/Foldable.hs b/lib/Ribosome/Data/Foldable.hs
new file mode 100644
--- /dev/null
+++ b/lib/Ribosome/Data/Foldable.hs
@@ -0,0 +1,12 @@
+module Ribosome.Data.Foldable(
+  findMapMaybeM,
+) where
+
+import Control.Monad (foldM)
+
+findMapMaybeM :: (Monad m, Foldable f) => (a -> m (Maybe b)) -> f a -> m (Maybe b)
+findMapMaybeM f fa =
+  foldM evaluate Nothing fa
+  where
+    evaluate (Just b) _ = return (Just b)
+    evaluate Nothing a = f a
diff --git a/lib/Ribosome/Log.hs b/lib/Ribosome/Log.hs
--- a/lib/Ribosome/Log.hs
+++ b/lib/Ribosome/Log.hs
@@ -1,14 +1,17 @@
 module Ribosome.Log(
   debug,
   info,
+  p,
 ) where
 
-import Control.Monad.IO.Class (liftIO)
+import Control.Monad.IO.Class (MonadIO, liftIO)
 import Neovim.Log (debugM, infoM)
-import Ribosome.Data.Ribo (Ribo)
 
-debug :: Show a => String -> a -> Ribo e ()
+debug :: (MonadIO m, Show a) => String -> a -> m ()
 debug name message = liftIO $ debugM name $ show message
 
-info :: Show a => String -> a -> Ribo e ()
+info :: (MonadIO m, Show a) => String -> a -> m ()
 info name message = liftIO $ infoM name $ show message
+
+p :: (MonadIO m, Show a) => a -> m ()
+p = liftIO . print
diff --git a/proteome.cabal b/proteome.cabal
--- a/proteome.cabal
+++ b/proteome.cabal
@@ -4,10 +4,10 @@
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: d351a462fd991c12f14990526d369c7f0c7e0497ef67def82fa7175b6b2a1f82
+-- hash: 64efc5d8d1ec76be44f4c5186571386396e63ec02ac038fbc71186e62cf26075
 
 name:           proteome
-version:        0.3.11.0
+version:        0.3.12.0
 synopsis:       neovim project manager
 description:    Please see the README on GitHub at <https://github.com/tek/proteome-hs>
 category:       Neovim
@@ -31,6 +31,7 @@
       Proteome.Add
       Proteome.BufEnter
       Proteome.Config
+      Proteome.Data.ActiveProject
       Proteome.Data.AddOptions
       Proteome.Data.Env
       Proteome.Data.Project
@@ -42,6 +43,7 @@
       Proteome.PersistBuffers
       Proteome.Plugin
       Proteome.Project
+      Proteome.Project.Activate
       Proteome.Project.Resolve
       Proteome.Save
       Proteome.Settings
@@ -51,10 +53,12 @@
       Proteome.Test.Unit
       Ribosome.Api.Buffer
       Ribosome.Api.Option
+      Ribosome.Api.Path
       Ribosome.Api.Response
       Ribosome.Config.Setting
       Ribosome.Config.Settings
       Ribosome.Data.Errors
+      Ribosome.Data.Foldable
       Ribosome.Data.Maybe
       Ribosome.Data.Ribo
       Ribosome.Data.Ribosome
@@ -84,6 +88,7 @@
     , data-default-class
     , deepseq
     , directory
+    , either
     , filepath
     , hslogger
     , lens
@@ -94,6 +99,7 @@
     , prettyprinter
     , process
     , resourcet
+    , safe
     , split
     , stm
     , strings
@@ -121,6 +127,7 @@
     , data-default-class
     , deepseq
     , directory
+    , either
     , filepath
     , hslogger
     , lens
@@ -132,6 +139,7 @@
     , process
     , proteome
     , resourcet
+    , safe
     , split
     , stm
     , strings
@@ -165,6 +173,7 @@
     , data-default-class
     , deepseq
     , directory
+    , either
     , filepath
     , hslogger
     , lens
@@ -176,6 +185,7 @@
     , process
     , proteome
     , resourcet
+    , safe
     , split
     , stm
     , strings
@@ -192,7 +202,8 @@
   other-modules:
       AddSpec
       Config
-      MultiSpec
+      CycleSpec
+      MultiTagsSpec
       PersistLoadSpec
       PersistStoreSpec
       Project
@@ -213,6 +224,7 @@
     , data-default-class
     , deepseq
     , directory
+    , either
     , filepath
     , hslogger
     , lens
@@ -224,6 +236,7 @@
     , process
     , proteome
     , resourcet
+    , safe
     , split
     , stm
     , strings
diff --git a/test/f/AddSpec.hs b/test/f/AddSpec.hs
--- a/test/f/AddSpec.hs
+++ b/test/f/AddSpec.hs
@@ -6,17 +6,27 @@
 
 import qualified Data.Map as Map (fromList)
 import Test.Framework
-import Neovim (vim_call_function, toObject)
+import Neovim (vim_call_function', vim_command', toObject)
 import Ribosome.Data.Ribo (Ribo)
 import Proteome.Test.Functional (specWith)
 import Config (vars)
 
 addSpec :: Ribo env ()
 addSpec = do
-  _ <- vim_call_function "ProAdd" [toObject $ Map.fromList [("name", "bar"), ("tpe", "foo")]]
+  _ <- vim_call_function' "ProAdd" [toObject $ Map.fromList [("name", "bar"), ("tpe", "foo")]]
   return ()
 
 test_add :: IO ()
 test_add = do
   v <- vars
   specWith v addSpec
+
+addCommandSpec :: Ribo env ()
+addCommandSpec = do
+  _ <- vim_command' "ProAdd! foo/bar"
+  return ()
+
+test_addCommand :: IO ()
+test_addCommand = do
+  v <- vars
+  specWith v addCommandSpec
diff --git a/test/u/CycleSpec.hs b/test/u/CycleSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/u/CycleSpec.hs
@@ -0,0 +1,52 @@
+{-# OPTIONS_GHC -F -pgmF htfpp #-}
+
+module CycleSpec(
+  htf_thisModulesTests
+) where
+
+import Control.Monad.IO.Class (liftIO)
+import System.FilePath ((</>))
+import Neovim (Neovim)
+import Test.Framework
+import Ribosome.Api.Path (nvimCwd)
+import Ribosome.Config.Setting (updateSetting)
+import Ribosome.Test.Unit (fixture)
+import Proteome.Add (proAdd)
+import Proteome.Project.Activate (proNext, proPrev)
+import Proteome.Data.AddOptions (AddOptions(AddOptions))
+import Proteome.Data.Proteome (Proteome)
+import Proteome.Init (resolveAndInitMain)
+import qualified Proteome.Settings as S (projectBaseDirs, mainProjectDir)
+import Proteome.Test.Unit (specWithDef)
+import Config (vars)
+import Project (cn, fn, tp, hask, prot, cil, flag)
+
+assertProject :: FilePath -> String -> Neovim e ()
+assertProject projectsDir n = do
+  cwd <- nvimCwd
+  liftIO $ assertEqual cwd $ projectsDir </> hask </> n
+
+cycleSpec :: Proteome ()
+cycleSpec = do
+  projectsDir <- fixture "projects"
+  let assertDir = assertProject projectsDir
+  let mainDir = projectsDir </> hask </> prot
+  updateSetting S.projectBaseDirs [projectsDir]
+  updateSetting S.mainProjectDir mainDir
+  resolveAndInitMain
+  proAdd $ AddOptions fn tp
+  proAdd $ AddOptions cn tp
+  assertDir prot
+  proNext
+  assertDir flag
+  proNext
+  assertDir cil
+  proNext
+  assertDir prot
+  proNext
+  assertDir flag
+  proPrev
+  assertDir prot
+
+test_next :: IO ()
+test_next = vars >>= specWithDef cycleSpec
diff --git a/test/u/MultiSpec.hs b/test/u/MultiSpec.hs
deleted file mode 100644
--- a/test/u/MultiSpec.hs
+++ /dev/null
@@ -1,49 +0,0 @@
-{-# OPTIONS_GHC -F -pgmF htfpp #-}
-
-module MultiSpec(
-  htf_thisModulesTests
-) where
-
-import Control.Monad.IO.Class (liftIO)
-import System.FilePath ((</>))
-import System.Directory (doesFileExist)
-import Test.Framework
-import Neovim (fromObject', vim_get_current_buffer', buffer_get_option')
-import Ribosome.Config.Setting (updateSetting)
-import Ribosome.Test.Unit (tempDir)
-import Proteome.Data.Proteome (Proteome)
-import Proteome.Add (proAdd)
-import Proteome.Data.AddOptions (AddOptions(AddOptions))
-import qualified Proteome.Settings as S (projectBaseDirs)
-import Proteome.Tags (proTags)
-import Proteome.Test.Unit (specWithDef)
-import Proteome.BufEnter (bufEnter)
-import qualified Proteome.Settings as PS (tagsCommand, tagsArgs, tagsFork)
-import Config (vars)
-import Project (flag, cil, hask, cn, fn, tp, createTestProject)
-
-multiSpec :: Proteome ()
-multiSpec = do
-  projectsDir <- tempDir "multi/projects"
-  updateSetting S.projectBaseDirs [projectsDir]
-  createTestProject tp fn
-  createTestProject tp cn
-  updateSetting PS.tagsCommand "touch"
-  updateSetting PS.tagsArgs ".tags"
-  updateSetting PS.tagsFork False
-  proAdd $ AddOptions fn tp
-  proAdd $ AddOptions cn tp
-  proTags
-  bufEnter
-  let ftags = projectsDir </> hask </> flag </> ".tags"
-  let ctags = projectsDir </> hask </> cil </> ".tags"
-  ftagsExists <- liftIO $ doesFileExist ftags
-  ctagsExists <- liftIO $ doesFileExist ctags
-  buf <- vim_get_current_buffer'
-  tags <- buffer_get_option' buf "tags" >>= fromObject'
-  liftIO $ assertEqual ftagsExists True
-  liftIO $ assertEqual ctagsExists True
-  liftIO $ assertEqual (ftags ++ "," ++ ctags) tags
-
-test_multi :: IO ()
-test_multi = vars >>= specWithDef multiSpec
diff --git a/test/u/MultiTagsSpec.hs b/test/u/MultiTagsSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/u/MultiTagsSpec.hs
@@ -0,0 +1,48 @@
+{-# OPTIONS_GHC -F -pgmF htfpp #-}
+
+module MultiTagsSpec(
+  htf_thisModulesTests
+) where
+
+import Control.Monad.IO.Class (liftIO)
+import System.FilePath ((</>))
+import System.Directory (doesFileExist)
+import Test.Framework
+import Neovim (fromObject', vim_get_current_buffer', buffer_get_option')
+import Ribosome.Config.Setting (updateSetting)
+import Ribosome.Test.Unit (tempDir)
+import Proteome.Data.Proteome (Proteome)
+import Proteome.Add (proAdd)
+import Proteome.Data.AddOptions (AddOptions(AddOptions))
+import qualified Proteome.Settings as S (projectBaseDirs, tagsCommand, tagsArgs, tagsFork)
+import Proteome.Tags (proTags)
+import Proteome.Test.Unit (specWithDef)
+import Proteome.BufEnter (bufEnter)
+import Config (vars)
+import Project (flag, cil, hask, cn, fn, tp, createTestProject)
+
+multiSpec :: Proteome ()
+multiSpec = do
+  projectsDir <- tempDir "multi/projects"
+  updateSetting S.projectBaseDirs [projectsDir]
+  createTestProject tp fn
+  createTestProject tp cn
+  updateSetting S.tagsCommand "touch"
+  updateSetting S.tagsArgs ".tags"
+  updateSetting S.tagsFork False
+  proAdd $ AddOptions fn tp
+  proAdd $ AddOptions cn tp
+  proTags
+  bufEnter
+  let ftags = projectsDir </> hask </> flag </> ".tags"
+  let ctags = projectsDir </> hask </> cil </> ".tags"
+  ftagsExists <- liftIO $ doesFileExist ftags
+  ctagsExists <- liftIO $ doesFileExist ctags
+  buf <- vim_get_current_buffer'
+  tags <- buffer_get_option' buf "tags" >>= fromObject'
+  liftIO $ assertEqual ftagsExists True
+  liftIO $ assertEqual ctagsExists True
+  liftIO $ assertEqual (ftags ++ "," ++ ctags) tags
+
+test_multi :: IO ()
+test_multi = vars >>= specWithDef multiSpec
diff --git a/test/u/Project.hs b/test/u/Project.hs
--- a/test/u/Project.hs
+++ b/test/u/Project.hs
@@ -7,6 +7,7 @@
   fn,
   cil,
   createTestProject,
+  prot,
 ) where
 
 import Control.Monad.IO.Class (liftIO)
@@ -29,6 +30,9 @@
 
 cil :: String
 cil = "cilia"
+
+prot :: String
+prot = "proteome"
 
 fn :: ProjectName
 fn = ProjectName flag
diff --git a/test/u/SpecMain.hs b/test/u/SpecMain.hs
--- a/test/u/SpecMain.hs
+++ b/test/u/SpecMain.hs
@@ -7,7 +7,8 @@
 import {-@ HTF_TESTS @-} PersistLoadSpec
 import {-@ HTF_TESTS @-} PersistStoreSpec
 import {-@ HTF_TESTS @-} AddSpec
-import {-@ HTF_TESTS @-} MultiSpec
+import {-@ HTF_TESTS @-} MultiTagsSpec
+import {-@ HTF_TESTS @-} CycleSpec
 import Test.Framework
 import Test.Framework.BlackBoxTest ()
 
