diff --git a/hix.cabal b/hix.cabal
--- a/hix.cabal
+++ b/hix.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           hix
-version:        0.4.2
+version:        0.5.0
 synopsis:       Haskell/Nix development build tools
 description:    See https://hackage.haskell.org/package/hix/docs/Hix.html
 category:       Build
@@ -44,6 +44,7 @@
       Hix.New
       Hix.Options
       Hix.Optparse
+      Hix.Path
       Hix.Prelude
       Hix.Preproc
   hs-source-dirs:
diff --git a/lib/Hix.hs b/lib/Hix.hs
--- a/lib/Hix.hs
+++ b/lib/Hix.hs
@@ -1,7 +1,5 @@
 module Hix where
 
-import Path.IO (getCurrentDir)
-
 import Hix.Bootstrap (bootstrapProject)
 import Hix.Data.Error (
   Error (..),
@@ -52,5 +50,4 @@
 main :: IO ()
 main = do
   Options global cmd <- parseCli
-  cwd <- getCurrentDir
-  leftA (handleError global) =<< runM cwd (runCommand cmd)
+  leftA (handleError global) =<< runM global.cwd (runCommand cmd)
diff --git a/lib/Hix/Bootstrap.hs b/lib/Hix/Bootstrap.hs
--- a/lib/Hix/Bootstrap.hs
+++ b/lib/Hix/Bootstrap.hs
@@ -150,8 +150,8 @@
   Path Abs Dir ->
   Path Rel File ->
   M CabalInfo
-readCabal root path = do
-  info <- liftIO (readGenericPackageDescription Cabal.verbose (toFilePath (root </> path)))
+readCabal cwd path = do
+  info <- liftIO (readGenericPackageDescription Cabal.verbose (toFilePath (cwd </> path)))
   pure CabalInfo {path = dir, info}
   where
     dir = parent path
@@ -387,9 +387,9 @@
 
 bootstrapFiles :: BootstrapProjectConfig -> M [ProjectFile]
 bootstrapFiles conf = do
-  Env {root} <- ask
-  cabals <- paths =<< lift (tryIO (getDirectoryFilesIgnore (toFilePath root) ["**/*.cabal"] ["dist-newstyle/**"]))
-  pkgs <- fmap convert <$> traverse (readCabal root) cabals
+  Env {cwd} <- ask
+  cabals <- paths =<< lift (tryIO (getDirectoryFilesIgnore (toFilePath cwd) ["**/*.cabal"] ["dist-newstyle/**"]))
+  pkgs <- fmap convert <$> traverse (readCabal cwd) cabals
   pure [
     ProjectFile {path = [relfile|flake.nix|], content = renderRootExpr (flake conf pkgs)}
     ]
diff --git a/lib/Hix/Component.hs b/lib/Hix/Component.hs
--- a/lib/Hix/Component.hs
+++ b/lib/Hix/Component.hs
@@ -1,12 +1,13 @@
 module Hix.Component where
 
-import Control.Monad.Trans.Reader (ask)
+import Control.Monad.Trans.Class (lift)
+import Control.Monad.Trans.Except (throwE)
 import Data.List.Extra (firstJust)
 import qualified Data.Map.Strict as Map
 import Data.Map.Strict ((!?))
 import qualified Data.Text as Text
 import Exon (exon)
-import Path (Abs, Dir, File, Path, Rel, SomeBase (Abs, Rel), isProperPrefixOf, stripProperPrefix)
+import Path (Abs, Dir, File, Path, Rel, SomeBase (Abs, Rel), isProperPrefixOf, reldir, stripProperPrefix)
 
 import qualified Hix.Data.ComponentConfig
 import Hix.Data.ComponentConfig (
@@ -17,9 +18,8 @@
   SourceDir (SourceDir),
   Target (Target),
   )
-import Hix.Data.Error (pathText)
-import qualified Hix.Monad as Monad
-import Hix.Monad (Env (Env), M, noteEnv)
+import Hix.Data.Error (Error (EnvError), pathText)
+import Hix.Monad (M, noteEnv)
 import qualified Hix.Options as Options
 import Hix.Options (
   ComponentCoords,
@@ -27,6 +27,7 @@
   PackageSpec (PackageSpec),
   TargetSpec (TargetForComponent, TargetForFile),
   )
+import Hix.Path (rootDir)
 
 tryPackageByDir ::
   PackagesConfig ->
@@ -44,13 +45,18 @@
 packageByDir config dir =
   noteEnv [exon|No package at this directory: #{pathText dir}|] (tryPackageByDir config dir)
 
+packageDefault :: PackagesConfig -> M PackageConfig
+packageDefault = \case
+  [(_, pkg)] -> pure pkg
+  _ -> lift (throwE (EnvError "Project has more than one package, specify -p or -f."))
+
 packageForSpec ::
+  Path Abs Dir ->
   PackagesConfig ->
   PackageSpec ->
   M PackageConfig
-packageForSpec config = \case
+packageForSpec root config = \case
   PackageSpec _ (Just (Abs dir)) -> do
-    Env {root} <- ask
     rel <- noteEnv [exon|Path is not a subdirectory of the project root: #{pathText dir}|] (stripProperPrefix root dir)
     packageByDir config rel
   PackageSpec (PackageName name) (Just (Rel dir)) | Text.elem '/' name ->
@@ -62,6 +68,15 @@
         Abs _ -> Nothing
         Rel rd -> tryPackageByDir config rd
 
+packageForSpecOrDefault ::
+  Path Abs Dir ->
+  PackagesConfig ->
+  Maybe PackageSpec ->
+  M PackageConfig
+packageForSpecOrDefault root config = \case
+  Just pkg -> packageForSpec root config pkg
+  Nothing -> packageDefault config
+
 matchComponent :: ComponentConfig -> ComponentSpec -> Bool
 matchComponent candidate (ComponentSpec name dir) =
   candidate.name == name || any (\ d -> elem @[] d (coerce candidate.sourceDirs)) dir
@@ -72,23 +87,47 @@
   where
     name = spec.name
 
+undecidableComponentError :: PackageName -> Text
+undecidableComponentError pname =
+  [exon|Please specify a component name or source dir with -c for the package '##{pname}'|]
+
+testComponent :: ComponentSpec
+testComponent =
+  ComponentSpec "test" (Just (SourceDir [reldir|test|]))
+
+targetInPackage ::
+  PackageConfig ->
+  Maybe ComponentSpec ->
+  M Target
+targetInPackage package = \case
+  Just comp -> do
+    component <- noteEnv (componentError package.name comp) (find match (Map.elems package.components))
+    pure Target {sourceDir = Nothing, ..}
+    where
+      match cand = matchComponent cand comp
+  Nothing -> do
+    component <- noteEnv (undecidableComponentError package.name) (selectComponent package.components)
+    pure Target {sourceDir = Nothing, ..}
+    where
+      selectComponent [(_, comp)] = Just comp
+      selectComponent (Map.elems -> comps) = find (match testComponent) comps
+      match = flip matchComponent
+
 targetForComponent ::
+  Path Abs Dir ->
   PackagesConfig ->
   ComponentCoords ->
   M Target
-targetForComponent config spec = do
-  package <- packageForSpec config spec.package
-  component <- noteEnv (componentError package.name spec.component) (find match (Map.elems package.components))
-  pure Target {sourceDir = Nothing, ..}
-  where
-    match = flip matchComponent spec.component
+targetForComponent root config spec = do
+  package <- packageForSpecOrDefault root config spec.package
+  targetInPackage package spec.component
 
 targetForFile ::
+  Path Abs Dir ->
   PackagesConfig ->
   Path Abs File ->
   M Target
-targetForFile config file = do
-  Env {root} <- ask
+targetForFile root config file = do
   fileRel <- stripProperPrefix root file
   (package, subpath) <- pkgError (firstJust (matchPackage fileRel) (Map.elems config))
   (component, sourceDir) <- compError (firstJust (matchSourceDir subpath) (Map.elems package.components))
@@ -104,12 +143,22 @@
     pkgError = noteEnv "No package contains this file"
     compError = noteEnv "No component source dir contains this file"
 
-targetComponent ::
+targetComponentIn ::
+  Path Abs Dir ->
   PackagesConfig ->
   TargetSpec ->
   M Target
-targetComponent config = \case
+targetComponentIn root config = \case
   TargetForComponent spec ->
-    targetForComponent config spec
+    targetForComponent root config spec
   TargetForFile spec ->
-    targetForFile config spec
+    targetForFile root config spec
+
+targetComponent ::
+  Maybe (Path Abs Dir) ->
+  PackagesConfig ->
+  TargetSpec ->
+  M Target
+targetComponent cliRoot config spec = do
+  root <- rootDir cliRoot
+  targetComponentIn root config spec
diff --git a/lib/Hix/Data/GhciConfig.hs b/lib/Hix/Data/GhciConfig.hs
--- a/lib/Hix/Data/GhciConfig.hs
+++ b/lib/Hix/Data/GhciConfig.hs
@@ -25,6 +25,10 @@
   deriving stock (Eq, Show, Generic)
   deriving newtype (IsList, Ord, FromJSON)
 
+newtype ChangeDir =
+  ChangeDir { unChangeDir :: Bool }
+  deriving stock (Eq, Show, Generic)
+
 data EnvConfig =
   EnvConfig {
     packages :: PackagesConfig,
diff --git a/lib/Hix/Data/ProjectFile.hs b/lib/Hix/Data/ProjectFile.hs
--- a/lib/Hix/Data/ProjectFile.hs
+++ b/lib/Hix/Data/ProjectFile.hs
@@ -19,9 +19,9 @@
 
 createFile :: ProjectFile -> M ()
 createFile f = do
-  Env {root} <- ask
+  Env {cwd} <- ask
   let
-    file = root </> f.path
+    file = cwd </> f.path
   lift $ tryIO do
     createDirIfMissing True (parent file)
     Text.writeFile (toFilePath file) f.content
diff --git a/lib/Hix/Env.hs b/lib/Hix/Env.hs
--- a/lib/Hix/Env.hs
+++ b/lib/Hix/Env.hs
@@ -1,6 +1,7 @@
 module Hix.Env where
 
 import qualified Data.Text.IO as Text
+import Path (Abs, Dir, Path)
 
 import Hix.Component (targetComponent)
 import qualified Hix.Data.ComponentConfig
@@ -12,15 +13,19 @@
 import qualified Hix.Options as Options
 import Hix.Options (EnvRunnerOptions, TargetSpec)
 
-componentRunner :: PackagesConfig -> TargetSpec -> M (Maybe EnvRunner)
-componentRunner config spec = do
-  Target {component} <- targetComponent config spec
+componentRunner ::
+  Maybe (Path Abs Dir) ->
+  PackagesConfig ->
+  TargetSpec ->
+  M (Maybe EnvRunner)
+componentRunner cliRoot config spec = do
+  Target {component} <- targetComponent cliRoot config spec
   pure component.runner
 
 envRunner :: EnvRunnerOptions -> M EnvRunner
 envRunner opts = do
   config <- either pure jsonConfig opts.config
-  fromMaybe config.defaultEnv . join <$> traverse (componentRunner config.packages) opts.component
+  fromMaybe config.defaultEnv . join <$> traverse (componentRunner opts.root config.packages) opts.component
 
 printEnvRunner :: EnvRunnerOptions -> M ()
 printEnvRunner opts = do
diff --git a/lib/Hix/Ghci.hs b/lib/Hix/Ghci.hs
--- a/lib/Hix/Ghci.hs
+++ b/lib/Hix/Ghci.hs
@@ -2,7 +2,6 @@
 
 import Control.Monad.Trans.Class (lift)
 import Control.Monad.Trans.Except (ExceptT, catchE)
-import Control.Monad.Trans.Reader (ask)
 import Data.List.Extra (nubOrd)
 import qualified Data.Map.Strict as Map
 import Data.Map.Strict ((!?))
@@ -10,7 +9,7 @@
 import qualified Data.Text.IO as Text
 import Exon (exon)
 import Path (Abs, Dir, File, Path, Rel, parseRelDir, reldir, splitExtension, stripProperPrefix, toFilePath, (</>))
-import Path.IO (createDirIfMissing, getCurrentDir, getTempDir, openTempFile)
+import Path.IO (createDirIfMissing, getTempDir, openTempFile)
 import System.IO (hClose)
 import System.Posix.User (getLoginName)
 
@@ -30,22 +29,18 @@
 import qualified Hix.Data.GhciTest as GhciTest
 import Hix.Data.GhciTest (GhciRun (GhciRun), GhciTest (GhciTest), GhcidRun (GhcidRun))
 import Hix.Json (jsonConfig)
-import qualified Hix.Monad as Monad
-import Hix.Monad (Env (Env), M, noteGhci, tryIOM)
+import Hix.Monad (M, noteGhci)
 import qualified Hix.Options as Options
-import Hix.Options (
-  GhciOptions (GhciOptions),
-  TargetSpec (TargetForComponent, TargetForFile),
-  TestOptions (TestOptions),
-  )
+import Hix.Options (GhciOptions (GhciOptions), TargetSpec (TargetForFile), TestOptions (TestOptions))
+import Hix.Path (rootDir)
 
 relativeToComponent ::
+  Path Abs Dir ->
   PackageConfig ->
   Maybe SourceDir ->
   Path Abs File ->
   M (Path Rel File)
-relativeToComponent package mdir path = do
-  Env {root} <- ask
+relativeToComponent root package mdir path = do
   SourceDir dir <- noteGhci "Internal: No source dir for file target" mdir
   noteGhci "Internal: Bad file target" (stripProperPrefix (root </> package.src </> dir) path)
 
@@ -55,10 +50,11 @@
   GhciOptions ->
   M ModuleName
 moduleName package component = \case
-  GhciOptions {component = TargetForComponent _, test} -> pure test.mod
-  GhciOptions {component = TargetForFile path} -> do
-    rel <- relativeToComponent package component path
+  GhciOptions {component = TargetForFile path, root = cliRoot} -> do
+    root <- rootDir cliRoot
+    rel <- relativeToComponent root package component path
     pure (ModuleName (Text.replace "/" "." (withoutExt rel)))
+  GhciOptions {test} -> pure test.mod
   where
     withoutExt p = pathText (maybe p fst (splitExtension p))
 
@@ -70,10 +66,13 @@
   M Text
 ghciScript config package component opt = do
   ModuleName module_ <- moduleName package component opt
-  pure [exon|#{setup}
+  pure [exon|#{cdCode}#{setup}
 :load #{module_}
 import #{module_}|]
   where
+    cdCode | opt.test.cd.unChangeDir = [exon|:cd #{pathText package.src}
+|]
+           | otherwise = ""
     GhciSetupCode setup = fold (flip Map.lookup config.setup =<< opt.test.runner)
 
 componentSearchPaths :: PackageConfig -> ComponentConfig -> [Path Rel Dir]
@@ -103,14 +102,14 @@
 assemble :: GhciOptions -> M GhciTest
 assemble opt = do
   config <- either pure jsonConfig opt.config
-  cwd <- tryIOM getCurrentDir
-  Target {..} <- targetComponent config.packages opt.component
+  root <- rootDir opt.root
+  Target {..} <- targetComponent opt.root config.packages opt.component
   script <- ghciScript config package sourceDir opt
   pure GhciTest {
     script,
     test = testRun config opt.test,
     args = config.args,
-    searchPath = (cwd </>) <$> searchPath config.packages package component
+    searchPath = (root </>) <$> searchPath config.packages package component
     }
 
 hixTempDir ::
diff --git a/lib/Hix/Monad.hs b/lib/Hix/Monad.hs
--- a/lib/Hix/Monad.hs
+++ b/lib/Hix/Monad.hs
@@ -9,7 +9,7 @@
 
 data Env =
   Env {
-    root :: Path Abs Dir
+    cwd :: Path Abs Dir
   }
   deriving stock (Eq, Show, Generic)
 
@@ -35,8 +35,6 @@
 runM root ma =
   runExceptT (runReaderT ma (Env root))
 
-tryIOM ::
-  IO a ->
-  M a
+tryIOM :: IO a -> M a
 tryIOM ma =
   lift (tryIO ma)
diff --git a/lib/Hix/Options.hs b/lib/Hix/Options.hs
--- a/lib/Hix/Options.hs
+++ b/lib/Hix/Options.hs
@@ -26,6 +26,7 @@
   value,
   )
 import Path (Abs, Dir, File, Path, SomeBase, parseRelDir, parseSomeDir)
+import Path.IO (getCurrentDir)
 import Prelude hiding (Mod, mod)
 
 import qualified Hix.Data.BootstrapProjectConfig
@@ -37,15 +38,16 @@
   PackageName (PackageName),
   SourceDir (SourceDir),
   )
-import Hix.Data.GhciConfig (EnvConfig, GhciConfig, RunnerName)
+import Hix.Data.GhciConfig (ChangeDir (ChangeDir), EnvConfig, GhciConfig, RunnerName)
 import qualified Hix.Data.NewProjectConfig
 import Hix.Data.NewProjectConfig (NewProjectConfig (NewProjectConfig))
 import Hix.Data.PreprocConfig (PreprocConfig)
-import Hix.Optparse (JsonConfig, absFileOption, jsonOption)
+import Hix.Optparse (JsonConfig, absDirOption, absFileOption, jsonOption)
 
 data PreprocOptions =
   PreprocOptions {
     config :: Maybe (Either PreprocConfig JsonConfig),
+    root :: Maybe (Path Abs Dir),
     source :: Path Abs File,
     inFile :: Path Abs File,
     outFile :: Path Abs File
@@ -68,8 +70,8 @@
 
 data ComponentCoords =
   ComponentCoords {
-    package :: PackageSpec,
-    component :: ComponentSpec
+    package :: Maybe PackageSpec,
+    component :: Maybe ComponentSpec
   }
   deriving stock (Eq, Show, Generic)
 
@@ -83,13 +85,15 @@
   TestOptions {
     mod :: ModuleName,
     test :: Maybe Text,
-    runner :: Maybe RunnerName
+    runner :: Maybe RunnerName,
+    cd :: ChangeDir
   }
   deriving stock (Eq, Show, Generic)
 
 data EnvRunnerOptions =
   EnvRunnerOptions {
     config :: Either EnvConfig JsonConfig,
+    root :: Maybe (Path Abs Dir),
     component :: Maybe TargetSpec
   }
   deriving stock (Show, Generic)
@@ -97,6 +101,7 @@
 data GhciOptions =
   GhciOptions {
     config :: Either GhciConfig JsonConfig,
+    root :: Maybe (Path Abs Dir),
     component :: TargetSpec,
     test :: TestOptions
   }
@@ -137,10 +142,10 @@
 
 data GlobalOptions =
   GlobalOptions {
-    verbose :: Maybe Bool
+    verbose :: Maybe Bool,
+    cwd :: Path Abs Dir
   }
   deriving stock (Eq, Show, Generic)
-  deriving anyclass (Default)
 
 data Options =
   Options {
@@ -156,10 +161,14 @@
 fileParser longName helpText =
   option absFileOption (long longName <> completer (bashCompleter "file") <> help helpText)
 
+rootParser :: Parser (Maybe (Path Abs Dir))
+rootParser =
+  optional (option absDirOption (long "root" <> help "The root directory of the project"))
+
 jsonConfigParser ::
   Parser JsonConfig
 jsonConfigParser =
-  option jsonOption (long "config" <> short 'c' <> help "The Hix-generated config, file or text")
+  option jsonOption (long "config" <> help "The Hix-generated config, file or text")
 
 preprocParser :: Parser PreprocOptions
 preprocParser =
@@ -167,26 +176,28 @@
   <$>
   (fmap Right <$> optional jsonConfigParser)
   <*>
+  rootParser
+  <*>
   fileParser "source" "The original source file"
   <*>
   fileParser "in" "The prepared input file"
   <*>
   fileParser "out" "The path to the output file"
 
-packageSpecParser :: Parser PackageSpec
+packageSpecParser :: Parser (Maybe PackageSpec)
 packageSpecParser = do
-  name <- strOption (long "package" <> short 'p' <> help "The name or directory of the test package")
-  pure PackageSpec {name = PackageName name, dir = parseSomeDir (toString name)}
+  optional (strOption (long "package" <> short 'p' <> help "The name or directory of the test package")) <&> fmap \ name ->
+    PackageSpec {name = PackageName name, dir = parseSomeDir (toString name)}
 
-componentSpecParser :: Parser ComponentSpec
+componentSpecParser :: Parser (Maybe ComponentSpec)
 componentSpecParser = do
-  name <- strOption (long "component" <> short 'c' <> help h <> value "test")
-  pure ComponentSpec {name = ComponentName name, dir = SourceDir <$> parseRelDir (toString name)}
+  optional (strOption (long "component" <> short 'c' <> help h)) <&> fmap \ name ->
+    ComponentSpec {name = ComponentName name, dir = SourceDir <$> parseRelDir (toString name)}
   where
     h = "The name or relative directory of the test component"
 
-componentForModuleParser :: Parser ComponentCoords
-componentForModuleParser =
+componentCoordsParser :: Parser ComponentCoords
+componentCoordsParser =
   ComponentCoords
   <$>
   packageSpecParser
@@ -201,9 +212,9 @@
 
 targetSpecParser :: Parser TargetSpec
 targetSpecParser =
-  TargetForComponent <$> componentForModuleParser
-  <|>
   componentForFileParser
+  <|>
+  TargetForComponent <$> componentCoordsParser
 
 envNameParser :: Parser EnvName
 envNameParser =
@@ -223,6 +234,10 @@
     help "The name of the command defined in the Hix option 'ghci.run'"
   ))
 
+cdParser :: Parser ChangeDir
+cdParser =
+  ChangeDir . not <$> switch (long "no-cd" <> help "Don't change the working directory to the package root")
+
 moduleParser :: Parser ModuleName
 moduleParser =
   strOption (long "module" <> short 'm' <> help "The module containing the test function" <> value "Main")
@@ -232,12 +247,14 @@
   test <- testParser
   runner <- runnerParser
   mod <- moduleParser
+  cd <- cdParser
   pure TestOptions {..}
 
 envParser :: Parser EnvRunnerCommandOptions
 envParser = do
   options <- do
     config <- Right <$> jsonConfigParser
+    root <- rootParser
     component <- optional targetSpecParser
     pure EnvRunnerOptions {..}
   test <- testOptionsParser
@@ -246,6 +263,7 @@
 ghciParser :: Parser GhciOptions
 ghciParser = do
   config <- Right <$> jsonConfigParser
+  root <- rootParser
   component <- targetSpecParser
   test <- testOptionsParser
   pure GhciOptions {..}
@@ -263,8 +281,7 @@
   hixUrl <- strOption (long "hix-url" <> help "The URL to the Hix repository" <> value def)
   pure BootstrapOptions {config = BootstrapProjectConfig {..}}
 
-commands ::
-  Mod CommandFields Command
+commands :: Mod CommandFields Command
 commands =
   command "preproc" (Preproc <$> info preprocParser (progDesc "Preprocess a source file for use with ghcid"))
   <>
@@ -278,20 +295,25 @@
   <>
   command "bootstrap" (BootstrapCmd <$> info bootstrapParser (progDesc "Bootstrap an existing Cabal project in the current directory"))
 
-globalParser :: Parser GlobalOptions
-globalParser = do
+globalParser ::
+  Path Abs Dir ->
+  Parser GlobalOptions
+globalParser realCwd = do
   verbose <- optional (switch (long "verbose" <> short 'v' <> help "Verbose output"))
+  cwd <- option absDirOption (long "cwd" <> help "Force a different working directory" <> value realCwd)
   pure GlobalOptions {..}
 
 appParser ::
+  Path Abs Dir ->
   Parser Options
-appParser =
-  Options <$> globalParser <*> hsubparser commands
+appParser cwd =
+  Options <$> globalParser cwd <*> hsubparser commands
 
 parseCli ::
   IO Options
 parseCli = do
-  customExecParser parserPrefs (info (appParser <**> helper) desc)
+  realCwd <- getCurrentDir
+  customExecParser parserPrefs (info (appParser realCwd <**> helper) desc)
   where
     parserPrefs =
       prefs (showHelpOnEmpty <> showHelpOnError)
diff --git a/lib/Hix/Optparse.hs b/lib/Hix/Optparse.hs
--- a/lib/Hix/Optparse.hs
+++ b/lib/Hix/Optparse.hs
@@ -5,7 +5,7 @@
 import Exon (exon)
 import Options.Applicative (ReadM, readerError)
 import Options.Applicative.Types (readerAsk)
-import Path (Abs, Dir, File, Path, Rel, parseAbsFile, parseRelDir, parseRelFile, toFilePath)
+import Path (Abs, Dir, File, Path, Rel, parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, toFilePath)
 import qualified Text.Show as Show
 
 -- |An absolute file path option for @optparse-applicative@.
@@ -19,6 +19,12 @@
 relFileOption = do
   raw <- readerAsk
   leftA (const (readerError [exon|not a valid relative file path: #{raw}|])) (parseRelFile raw)
+
+-- |A relative dir path option for @optparse-applicative@.
+absDirOption :: ReadM (Path Abs Dir)
+absDirOption = do
+  raw <- readerAsk
+  leftA (const (readerError [exon|not a valid absolute dir path: #{raw}|])) (parseAbsDir raw)
 
 -- |A relative dir path option for @optparse-applicative@.
 relDirOption :: ReadM (Path Rel Dir)
diff --git a/lib/Hix/Path.hs b/lib/Hix/Path.hs
new file mode 100644
--- /dev/null
+++ b/lib/Hix/Path.hs
@@ -0,0 +1,25 @@
+module Hix.Path where
+
+import Control.Monad.Trans.Reader (ask)
+import Path (Abs, Dir, Path, parent, toFilePath)
+import System.FilePattern.Directory (getDirectoryFiles)
+import System.IO.Error (tryIOError)
+
+import qualified Hix.Monad
+import Hix.Monad (Env (Env), M)
+
+findFlake :: Path Abs Dir -> IO (Maybe (Path Abs Dir))
+findFlake cur =
+  tryIOError (getDirectoryFiles (toFilePath cur) ["flake.nix"]) >>= either (const (pure Nothing)) \case
+    [_] -> pure (Just cur)
+    _ | parent cur == cur -> pure Nothing
+    _ | otherwise -> findFlake (parent cur)
+
+inferRoot :: M (Path Abs Dir)
+inferRoot = do
+  Env {cwd} <- ask
+  fromMaybe cwd <$> liftIO (findFlake cwd)
+
+rootDir :: Maybe (Path Abs Dir) -> M (Path Abs Dir)
+rootDir =
+  maybe inferRoot pure
diff --git a/lib/Hix/Preproc.hs b/lib/Hix/Preproc.hs
--- a/lib/Hix/Preproc.hs
+++ b/lib/Hix/Preproc.hs
@@ -17,7 +17,7 @@
   Extension (DisableExtension, EnableExtension, UnknownExtension),
   Language (UnknownLanguage),
   )
-import Path (Abs, File, Path, toFilePath)
+import Path (Abs, Dir, File, Path, toFilePath)
 import Prelude hiding (group)
 import System.Random (randomRIO)
 
@@ -446,10 +446,14 @@
   let result = preprocessModule opt.source conf dummyExportName inLines
   lift (tryIO (ByteStringBuilder.writeFile (toFilePath opt.outFile) result))
 
-fromConfig :: Path Abs File -> Either PreprocConfig JsonConfig -> M CabalConfig
-fromConfig source pconf = do
+fromConfig ::
+  Maybe (Path Abs Dir) ->
+  Path Abs File ->
+  Either PreprocConfig JsonConfig ->
+  M CabalConfig
+fromConfig cliRoot source pconf = do
   conf <- either pure jsonConfig pconf
-  target <- targetComponent conf.packages (TargetForFile source)
+  target <- targetComponent cliRoot conf.packages (TargetForFile source)
   pure CabalConfig {
     extensions = stringUtf8 <$> target.component.language : target.component.extensions,
     ghcOptions = stringUtf8 <$> target.component.ghcOptions,
@@ -476,5 +480,5 @@
 -- TODO add common stanzas
 preprocess :: PreprocOptions -> M ()
 preprocess opt = do
-  conf <- maybe (fromCabalFile opt.source) (fromConfig opt.source) opt.config
+  conf <- maybe (fromCabalFile opt.source) (fromConfig opt.root opt.source) opt.config
   preprocessWith opt conf
diff --git a/test/Hix/Test/GhciTest.hs b/test/Hix/Test/GhciTest.hs
--- a/test/Hix/Test/GhciTest.hs
+++ b/test/Hix/Test/GhciTest.hs
@@ -4,7 +4,7 @@
 import Exon (exon)
 import Hedgehog (TestT, evalEither, (===))
 import Path (Abs, Dir, File, Path, Rel, SomeBase (Rel), absdir, absfile, reldir, relfile, (</>))
-import Path.IO (getCurrentDir, withSystemTempDir)
+import Path.IO (withSystemTempDir)
 
 import Hix.Data.ComponentConfig (
   ComponentConfig (..),
@@ -16,7 +16,7 @@
   SourceDirs (SourceDirs),
   )
 import Hix.Data.Error (pathText)
-import Hix.Data.GhciConfig (EnvConfig (EnvConfig), GhciConfig (..))
+import Hix.Data.GhciConfig (ChangeDir (ChangeDir), EnvConfig (EnvConfig), GhciConfig (..))
 import qualified Hix.Data.GhciTest as GhciTest
 import Hix.Env (envRunner)
 import Hix.Ghci (assemble, ghcidCmdlineFromOptions)
@@ -85,8 +85,8 @@
 spec1 :: TargetSpec
 spec1 =
   TargetForComponent ComponentCoords {
-    package = PackageSpec "api" (Just (Rel [reldir|api|])),
-    component = ComponentSpec "test" (Just (SourceDir [reldir|test|]))
+    package = Just (PackageSpec "api" (Just (Rel [reldir|api|]))),
+    component = Just (ComponentSpec "test" (Just (SourceDir [reldir|test|])))
   }
 
 options :: GhciOptions
@@ -98,11 +98,13 @@
       run = [("generic", ("check . property . test"))],
       args = ["-Werror"]
     },
+    root = Nothing,
     component = spec1,
     test = TestOptions {
       mod = "Api.ServerTest",
       test = Just "test_server",
-      runner = Just "generic"
+      runner = Just "generic",
+      cd = ChangeDir True
     }
   }
 
@@ -119,11 +121,10 @@
 
 test_ghcid :: TestT IO ()
 test_ghcid = do
-  cwd <- getCurrentDir
   res <- lift $ withSystemTempDir "hix-test" \ tmp ->
     runM root (ghcidCmdlineFromOptions tmp options)
   cmdline <- evalEither res
-  ghcidTarget cwd cmdline.ghci.scriptFile === cmdline.cmdline
+  ghcidTarget root cmdline.ghci.scriptFile === cmdline.cmdline
 
 spec2 :: TargetSpec
 spec2 =
@@ -132,8 +133,8 @@
 spec3 :: TargetSpec
 spec3 =
   TargetForComponent ComponentCoords {
-    package = PackageSpec "packages/core" (Just (Rel [reldir|packages/core|])),
-    component = ComponentSpec "core-test" (Just (SourceDir [reldir|core-test|]))
+    package = Just (PackageSpec "packages/core" (Just (Rel [reldir|packages/core|]))),
+    component = Just (ComponentSpec "core-test" (Just (SourceDir [reldir|core-test|])))
   }
 
 runnerFor :: EnvRunner -> TargetSpec -> TestT IO ()
@@ -141,7 +142,7 @@
   res <- evalEither =<< liftIO (runM root (envRunner conf))
   target === res
   where
-    conf = EnvRunnerOptions (Left (EnvConfig packages defaultRunner)) (Just spec)
+    conf = EnvRunnerOptions (Left (EnvConfig packages defaultRunner)) Nothing (Just spec)
 
 test_componentEnv :: TestT IO ()
 test_componentEnv = do
@@ -155,7 +156,8 @@
 
 target_moduleName :: Text
 target_moduleName =
-  [exon|import Test.Tasty
+  [exon|:cd packages/core/
+import Test.Tasty
 :load #{m}
 import #{m}|]
   where
