diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
 # Changelog
 
+## 0.3 (2020-05-07)
+- only read Hackage username/password if not in ~/.cabal/config
+- 'upload': show newer untagged commits
+- 'dist': new command for a manually tagged release
+- fix the check for package version committed
+
 ## 0.2.7 (2020-02-27)
 - Main.hs: explicitly export main and add SPDX-License-Identifier
 - move Main.hs to src/
diff --git a/Main.hs b/Main.hs
deleted file mode 100644
--- a/Main.hs
+++ /dev/null
@@ -1,362 +0,0 @@
-{-# LANGUAGE CPP #-}
-
-module Main (main) where
-
-#if !MIN_VERSION_base(4,8,0)
-import Control.Applicative (pure, (<$>))
-#endif
-
-import Control.Exception (bracket_, finally, onException)
-import Control.Monad.Extra
-import qualified Data.ByteString.Lazy.Char8 as B
-import Data.Char
-import Data.List.Extra
-import Data.Maybe
-#if !MIN_VERSION_base(4,11,0)
-import Data.Monoid ((<>))
-#endif
-import SimpleCabal
-import SimpleCmdArgs
-import System.Directory
-import System.Environment.XDG.BaseDir
-import System.Exit (ExitCode (..))
-import System.FilePath
-import System.IO (BufferMode(NoBuffering), hSetBuffering, hSetEcho,
-                  stdin, stdout)
-import qualified System.Process.Typed as P
-import Paths_hkgr (getDataFileName, version)
-
-main :: IO ()
-main = do
-  hSetBuffering stdout NoBuffering
-  simpleCmdArgs (Just version) "Hackage Release tool"
-    "'Hackager' is a package release tool for easy Hackage workflow" $
-    subcommands
-    [ Subcommand "new" "setup a new project" $
-      newCmd <$> optional (strArg "PROJECT")
-    , Subcommand "tagdist" "'git tag' version and 'cabal sdist' tarball" $
-      tagDistCmd <$> forceOpt "Move existing tag"
-    , Subcommand "upload" "'cabal upload' candidate tarball to Hackage" $
-      uploadCmd False <$> forceOpt "Move existing tag"
-    , Subcommand "publish" "Publish to Hackage ('cabal upload --publish')" $
-      pure $ uploadCmd True False
-    , Subcommand "upload-haddock" "Upload candidate documentation to Hackage" $
-      pure $ upHaddockCmd False
-    , Subcommand "publish-haddock" "Publish documentation to Hackage" $
-      pure $ upHaddockCmd True
-    , Subcommand "version" "Show the package version from .cabal file" $
-      pure showVersionCmd
-    ]
-  where
-    forceOpt = switchWith 'f' "force"
-
-git :: String -> [String] -> P.ProcessConfig () () ()
-git c args = P.proc "git" (c:args)
-
-git_ :: String -> [String] -> IO ()
-git_ c args =
-  cmd_ "git" (c:args)
-
-gitBool :: String -> [String] -> IO Bool
-gitBool c args = do
-  (== ExitSuccess) <$> P.runProcess (git c args)
-
-removeTrailingNewline :: B.ByteString -> B.ByteString
-removeTrailingNewline "" = ""
-removeTrailingNewline bs =
-  if B.last bs == '\n'
-  then B.init bs
-  else bs
-
--- cmd :: String -> [String] -> IO String
--- cmd c args =
---   B.unpack . removeTrailingNewline <$> P.readProcessStdout_ (P.proc c args)
-
-cmd_ :: String -> [String] -> IO ()
-cmd_ c args = P.runProcess_ $ P.proc c args
-
-cmdOut :: P.ProcessConfig () () () -> IO String
-cmdOut p =
-  B.unpack . removeTrailingNewline <$> P.readProcessStdout_ p
-
-cmdMaybe :: P.ProcessConfig () () () -> IO (Maybe String)
-cmdMaybe p = do
-  (ret, out ,_err) <- P.readProcess p
-  return $ if ret == ExitSuccess
-           then Just (B.unpack (removeTrailingNewline out))
-           else Nothing
-
--- from simple-cmd
-error' :: String -> a
-#if (defined(MIN_VERSION_base) && MIN_VERSION_base(4,9,0))
-error' = errorWithoutStackTrace
-#else
-error' = error
-#endif
-
-tagDistCmd :: Bool -> IO ()
-tagDistCmd force = do
-  needProgram "cabal"
-  diff <- cmdOut $ git "diff" ["HEAD"]
-  unless (null diff) $ do
-    putStrLn "=== start of uncommitted changes ==="
-    putStrLn diff
-    putStrLn "=== end of uncommitted changes ==="
-  pkgid <- checkPackage
-  let tag = pkgidTag pkgid
-  tagHash <- cmdMaybe (git "rev-parse" [tag])
-  when (isJust tagHash && not force) $
-    error' $ "tag " ++ tag ++ " exists: use --force to override and move"
-  git_ "tag" $ ["--force" | force] ++ [tag]
-  unless force $ putStrLn tag
-  sdist force pkgid `onException` do
-    putStrLn "Resetting tag"
-    if force
-      then git_ "tag" ["--force", tag, fromJust tagHash]
-      else git_ "tag" ["--delete", tag]
-
-pkgidTag :: PackageIdentifier -> String
-pkgidTag pkgid = "v" ++ packageVersion pkgid
-
-checkPackage :: IO PackageIdentifier
-checkPackage = do
-  pkgid <- getPackageId
-  checkNameVersionCommitted pkgid
-  checkNotPublished pkgid
-  return pkgid
-  where
-    checkNameVersionCommitted :: PackageIdentifier -> IO ()
-    checkNameVersionCommitted pkgid = do
-      let pkg = packageName pkgid
-          cabalfile = unPackageName pkg <.> "cabal"
-      unlessM (doesFileExist cabalfile) $
-        error' $ ".cabal filename differs from package name: " ++ unPackageName pkg
-      diff <- cmdOut (git "diff" ["-U0", "HEAD", cabalfile])
-      when ("version:" `isPrefixOf` map toLower diff) $
-        error' "Please commit or revert the package Version first"
-
-    checkNotPublished :: PackageIdentifier -> IO ()
-    checkNotPublished pkgid = do
-      let published = sdistDir </> showPkgId pkgid <.> ".tar.gz" <.> "published"
-      exists <- doesFileExist published
-      when exists $ error' $ showPkgId pkgid <> " was already published!!"
-      let oldpublished = "dist" </> showPkgId pkgid <.> ".tar.gz" <.> "published"
-      oldExists <- doesFileExist oldpublished
-      when oldExists $ error' $ showPkgId pkgid <> " was already published!!"
-
-sdistDir :: FilePath
-sdistDir = ".hkgr"
-
-sdist :: Bool -> PackageIdentifier -> IO ()
-sdist force pkgid = do
-  let tag = pkgidTag pkgid
-  let target = sdistDir </> showPkgId pkgid <.> ".tar.gz"
-  haveTarget <- doesFileExist target
-  when haveTarget $
-    if force
-    then removeFile target
-    else error' $ target <> " exists already!"
-  cwd <- getCurrentDirectory
-  withTempDirectory "tmp-sdist" $ do
-    git_ "clone" ["-q", "--no-checkout", "..", "."]
-    git_ "checkout" ["-q", tag]
-    whenM (doesFileExist ".gitmodules") $
-      git_ "submodule" ["update", "--init"]
-    cabal_ "check" []
-    mhlint <- findExecutable "hlint"
-    when (isJust mhlint) $ do
-      putStrLn "Running hlint"
-      void $ P.runProcess $ P.proc "hlint" ["--no-summary", "."]
-    let dest = takeDirectory $ cwd </> target
-    unlessM (doesDirectoryExist dest) $
-      createDirectoryIfMissing True dest
-    cabal_ "v2-sdist" ["--output-dir=" ++ dest]
-
-showVersionCmd :: IO ()
-showVersionCmd = do
-  pkgid <- getPackageId
-  putStrLn $ packageVersion pkgid
-
--- FIXME cabal install creates tarballs now
-uploadCmd :: Bool -> Bool -> IO ()
-uploadCmd publish force = do
-  pkgid <- checkPackage
-  let file = sdistDir </> showPkgId pkgid <.> ".tar.gz"
-      tag = pkgidTag pkgid
-  exists <- doesFileExist file
-  when (force || not exists) $
-    tagDistCmd force
-  whenM (null <$> cmdOut (git "branch" ["--contains", "tags/" ++ tag])) $
-    error' $ tag ++ " is no longer on branch: use --force to move it"
-  when publish $ do
-    tagHash <- cmdOut $ git "rev-parse" [tag]
-    branch <- cmdOut $ git "branch" ["--show-current"]
-    mergeable <- gitBool "merge-base" ["--is-ancestor", "HEAD", tagHash]
-    when mergeable $ do
-      putStr "git pushing... "
-      git_ "push" ["--quiet", "origin", tagHash ++ ":" ++ branch]
-      putStrLn "done"
-    git_ "push" ["origin", tag]
-  username <- prompt False "hackage.haskell.org username"
-  passwd <- prompt True "hackage.haskell.org password"
-  let userpassBS = B.pack $ unlines [username, passwd]
-  void $ P.readProcessInterleaved_ (P.setStdin (P.byteStringInput userpassBS) $ P.proc "cabal" ("upload" : ["--publish" | publish] ++ [file]))
-  putStrLn $ (if publish then "Published at " else "Uploaded to ") ++ "https://hackage.haskell.org/package/" ++ showPkgId pkgid ++ if publish then "" else "/candidate"
-  when publish $
-    createFileLink (takeFileName file) (file <.> "published")
-
--- maybeGetUserPassword :: IO (Maybe String, Maybe String)
--- maybeGetUserPassword = do
---   muser <- maybeGetUsername
---   mpwd <- maybeGetPassword
---   return (muser,mpwd)
---   where
---     maybeGetUsername :: IO (Maybe String)
-
-prompt :: Bool -> String -> IO String
-prompt hide s = do
-  putStr $ s ++ ": "
-  inp <- if hide then withoutEcho getLine else getLine
-  when hide $ putChar '\n'
-  return inp
-  where
-    withoutEcho :: IO a -> IO a
-    withoutEcho action =
-      finally (hSetEcho stdin False >> action) (hSetEcho stdin True)
-
-upHaddockCmd :: Bool -> IO ()
-upHaddockCmd publish = do
-  username <- prompt False "hackage.haskell.org username"
-  passwd <- prompt True "hackage.haskell.org password"
-  let userpassBS = B.pack $ unlines [username, passwd]
-  _out <- P.readProcessInterleaved_ (P.setStdin (P.byteStringInput userpassBS) $ P.proc "cabal" ("upload" : "--documentation" : ["--publish" | publish]))
-
-  pkgid <- getPackageId
-  putStrLn $ (if publish then "Published at " else "Uploaded to ") ++ "https://hackage.haskell.org/package/" ++ showPkgId pkgid ++ if publish then "" else "/candidate"
-
-cabal_ :: String -> [String] -> IO ()
-cabal_ c args =
-  cmd_ "cabal" (c:args)
-
-withTempDirectory :: FilePath -> IO a -> IO a
-withTempDirectory dir run =
-  bracket_ (createDirectory dir) (removeDirectoryRecursive dir) $
-  withCurrentDirectory dir run
-
--- #if (defined(MIN_VERSION_simple_cmd) && MIN_VERSION_simple_cmd(0,2,1))
--- #else
-needProgram :: String -> IO ()
-needProgram prog = do
-  mx <- findExecutable prog
-  unless (isJust mx) $ error' $ "program needs " ++ prog
--- #endif
-
--- FIXME warning if upstream template changed (keep a versioned template copy)
--- FIXME add default templates dir
--- FIXME --license
-newCmd :: Maybe String -> IO ()
-newCmd mproject = do
-  name <- case mproject of
-    Just ns -> return ns
-    Nothing -> do
-      files <- listDirectory "."
-      if null files then takeFileName <$> getCurrentDirectory
-        else do
-        -- filter out dirs
-        dirs <- filterM doesDirectoryExist files
-        if dirs == files then error' "Could not guess name"
-          else
-          case filter ("cabal" `isExtensionOf`) $ files \\ dirs of
-            [] -> takeFileName <$> getCurrentDirectory
-            [cbl] -> return $ takeBaseName cbl
-            _ -> error' "More than one .cabal file found!"
-  when (isJust mproject) $ createDirectory name >> setCurrentDirectory name
-  mcabal <- checkForCabalFile
-  case mcabal of
-    Nothing -> do
-      let setupFile = "Setup.hs"
-      origsetup <- doesFileExist setupFile
-      cabal_ "init" ["--quiet", "--no-comments", "--non-interactive", "--is-libandexe", "--cabal-version=1.18", "--license=BSD3", "--package-name=" ++ name, "--version=0.1.0", "--dependency=base<5", "--source-dir=src"]
-      whenJustM (cmdMaybe $ P.proc "find" ["-name", "Main.hs"]) $ \ file -> do
-        sed ["1s/^module Main where/-- SPDX-License-Identifier: BSD-3-Clause\\n\\nmodule Main (main) where/"] file
-        unless (file == "src/Main.hs") $ renameFile file "src/Main.hs"
-      whenM (doesFileExist "CHANGELOG.md") $
-        renameFile "CHANGELOG.md" "ChangeLog.md"
-      unlessM (doesFileExist "README.md") $
-        writeFile "README.md" $ "# " ++ name ++ "\n"
-      unless origsetup $ do
-        setup <- doesFileExist setupFile
-        when setup $ removeFile setupFile
-      setupCabalTemplate name
-    Just cblName ->
-      when (cblName /= name) $
-      putStrLn $ "Warning: " ++ cblName ++ " different to " ++ name
-  haveStackCfg <- doesFileExist "stack.yaml"
-  mstack <- findExecutable "stack"
-  -- FIXME add stack.yaml template too
-  when (not haveStackCfg && isJust mstack) $ do
-    cmd_ "stack" ["init", "--verbosity", "warn", "--resolver", "lts-16"]
-    sed ["/^#/d", "/^$/d"] "stack.yaml"
-  haveGit <- doesDirectoryExist ".git"
-  unless haveGit $ do
-    git_ "init" ["-q"]
-    git_ "add" [name <.> "cabal"]
-  where
-    checkForCabalFile :: IO (Maybe String)
-    checkForCabalFile = do
-      files <- listDirectory "."
-      case filter ("cabal" `isExtensionOf`) files of
-        [] -> return Nothing
-        [cbl] -> return $ Just (takeBaseName cbl)
-        _ -> error' "More than one .cabal file found!"
-
-    sed :: [String] -> FilePath -> IO ()
-    sed [] _ = error' "sed given no script"
-    sed args file =
-      cmd_ "sed" $ ["-i", "-e"] ++ intersperse "-e" args ++ [file]
-
-    setupCabalTemplate :: String -> IO ()
-    setupCabalTemplate name = do
-      userTemplate <- getUserConfigFile "hkgr" "template.cabal"
-      unlessM (doesFileExist userTemplate) $ do
-        origTemplate <- getDataFileName "template.cabal.tmpl"
-        createDirectoryIfMissing True $ takeDirectory userTemplate
-        -- FIXME put a copy of the current template there too for reference
-        copyFile origTemplate userTemplate
-        username <- cmdOut $ git "config" ["--global", "user.name"]
-        replaceHolder "NAME" username userTemplate
-        usermail <-
-          fromMaybeM (cmdOut $ git "config" ["--global", "user.email"]) $
-            cmdMaybe $ git "config" ["--global", "github.email"]
-        replaceHolder "EMAIL" usermail userTemplate
-        githubuser <- cmdOut $ git "config" ["--global", "github.user"]
-        replaceHolder "USER" githubuser userTemplate
-        putStrLn $ userTemplate ++ " set up"
-      copyFile userTemplate $ name <.> "cabal"
-      replaceHolder "PROJECT" name $ name <.> "cabal"
-      replaceHolder "PROJECT_" (map underscore name) $ name <.> "cabal"
-      replaceHolder "SUMMARY" (name ++ " project") $ name <.> "cabal"
-      year <- cmdOut (P.proc "date" ["+%Y"])
-      replaceHolder "YEAR" year $ name <.> "cabal"
-      let modulePath = "src/MyLib.hs"
-      unlessM (doesFileExist modulePath) $ do
-        createDirectoryIfMissing True $ takeDirectory modulePath
-        writeFile modulePath "-- SPDX-License-Identifier: BSD-3-Clause\n\nmodule MyLib where\n"
-        -- sed ["-i", "1s/^/-- SPDX-License-Identifier: BSD-3-Clause\n\n/"] modulePath
-      where
-        replaceHolder lbl val file =
-          sed ["s/@" ++ lbl ++ "@/" ++ val ++ "/"] file
-
-        underscore '-' = '_'
-        underscore c = c
-
-#if !MIN_VERSION_filepath(1,4,2)
-    isExtensionOf :: String -> FilePath -> Bool
-    isExtensionOf ext@('.':_) = isSuffixOf ext . takeExtensions
-    isExtensionOf ext         = isSuffixOf ('.':ext) . takeExtensions
-#endif
-
-#if !MIN_VERSION_extra(1,6,15)
-fromMaybeM :: Monad m => m a -> m (Maybe a) -> m a
-fromMaybeM n = maybeM n return
-#endif
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -46,8 +46,10 @@
 Source tarball created: dist/hkgr-0.2.5.tar.gz
 ```
 
-The tarball can now be uploaded to Hackage as a candidate release:
+Alternatively if you have manually tagged the version
+with `vX.Y.Z` you can use `hkgr dist` to create a tarball.
 
+The tarball can now be uploaded to Hackage as a candidate release:
 ```
 $ hkgr upload
 
@@ -93,6 +95,10 @@
 (ie released) version made with hkgr.
 
 If sdist fails for some reason then hkgr tries to reset the tag.
+
+### dist
+Alternatively if you have already manually tagged a release with 'v' prefix
+you can use `hkgr dist` to create a tarball.
 
 ### upload
 `hkgr upload` uploads the tarball to Hackage as a candidate release.
diff --git a/hkgr.cabal b/hkgr.cabal
--- a/hkgr.cabal
+++ b/hkgr.cabal
@@ -1,6 +1,6 @@
 cabal-version:       2.0
 name:                hkgr
-version:             0.2.7
+version:             0.3
 synopsis:            Simple Hackage release workflow for package maintainers
 description:
             Hkgr (pronounced "Hackager") is a tool to help make new releases of
@@ -13,14 +13,14 @@
 license-file:        LICENSE
 author:              Jens Petersen
 maintainer:          juhpetersen@gmail.com
-copyright:           2019-2020  Jens Petersen
+copyright:           2019-2021  Jens Petersen
 category:            Util
 build-type:          Simple
 data-dir:            data
 data-files:          template.cabal.tmpl
 extra-doc-files:     README.md
                    , CHANGELOG.md
-tested-with:         GHC == 8.8.3, GHC == 8.6.5, GHC == 8.4.4, GHC == 8.2.2,
+tested-with:         GHC == 8.8.4, GHC == 8.6.5, GHC == 8.4.4, GHC == 8.2.2,
                      GHC == 8.0.2
 
 source-repository head
@@ -28,7 +28,7 @@
   location:            https://github.com/juhp/hkgr.git
 
 executable hkgr
-  main-is:             Main.hs
+  main-is:             src/Main.hs
   other-modules:       Paths_hkgr
   autogen-modules:     Paths_hkgr
 
diff --git a/src/Main.hs b/src/Main.hs
new file mode 100644
--- /dev/null
+++ b/src/Main.hs
@@ -0,0 +1,396 @@
+{-# LANGUAGE CPP #-}
+
+module Main (main) where
+
+#if !MIN_VERSION_base(4,8,0)
+import Control.Applicative (pure, (<$>))
+#endif
+
+import Control.Exception (bracket_, finally, onException)
+import Control.Monad.Extra
+import qualified Data.ByteString.Lazy.Char8 as B
+import Data.Char
+import Data.List.Extra
+import Data.Maybe
+#if !MIN_VERSION_base(4,11,0)
+import Data.Monoid ((<>))
+#endif
+import SimpleCabal
+import SimpleCmdArgs
+import System.Directory
+import System.Environment.XDG.BaseDir
+import System.Exit (ExitCode (..))
+import System.FilePath
+import System.IO (BufferMode(NoBuffering), hSetBuffering, hSetEcho,
+                  stdin, stdout)
+import qualified System.Process.Typed as P
+import Paths_hkgr (getDataFileName, version)
+
+main :: IO ()
+main = do
+  hSetBuffering stdout NoBuffering
+  simpleCmdArgs (Just version) "Hackage Release tool"
+    "'Hackager' is a package release tool for easy Hackage workflow" $
+    subcommands
+    [ Subcommand "new" "setup a new project" $
+      newCmd <$> optional (strArg "PROJECT")
+    , Subcommand "tagdist" "'git tag' version and 'cabal sdist' tarball" $
+      tagDistCmd False <$> forceOpt "Move existing tag"
+    , Subcommand "dist" "'cabal sdist' tarball" $
+      tagDistCmd True <$> forceOpt "Recreate tarball"
+    , Subcommand "upload" "'cabal upload' candidate tarball to Hackage" $
+      uploadCmd False <$> forceOpt "Move existing tag"
+    , Subcommand "publish" "Publish to Hackage ('cabal upload --publish')" $
+      pure $ uploadCmd True False
+    , Subcommand "upload-haddock" "Upload candidate documentation to Hackage" $
+      pure $ upHaddockCmd False
+    , Subcommand "publish-haddock" "Publish documentation to Hackage" $
+      pure $ upHaddockCmd True
+    , Subcommand "version" "Show the package version from .cabal file" $
+      pure showVersionCmd
+    ]
+  where
+    forceOpt = switchWith 'f' "force"
+
+git :: String -> [String] -> P.ProcessConfig () () ()
+git c args = P.proc "git" (c:args)
+
+git_ :: String -> [String] -> IO ()
+git_ c args =
+  cmd_ "git" (c:args)
+
+gitBool :: String -> [String] -> IO Bool
+gitBool c args = do
+  (== ExitSuccess) <$> P.runProcess (git c args)
+
+removeTrailingNewline :: B.ByteString -> B.ByteString
+removeTrailingNewline "" = ""
+removeTrailingNewline bs =
+  if B.last bs == '\n'
+  then B.init bs
+  else bs
+
+-- cmd :: String -> [String] -> IO String
+-- cmd c args =
+--   B.unpack . removeTrailingNewline <$> P.readProcessStdout_ (P.proc c args)
+
+cmd_ :: String -> [String] -> IO ()
+cmd_ c args = P.runProcess_ $ P.proc c args
+
+cmdOut :: P.ProcessConfig () () () -> IO String
+cmdOut p =
+  B.unpack . removeTrailingNewline <$> P.readProcessStdout_ p
+
+cmdMaybe :: P.ProcessConfig () () () -> IO (Maybe String)
+cmdMaybe p = do
+  (ret, out ,_err) <- P.readProcess p
+  return $ if ret == ExitSuccess
+           then Just (B.unpack (removeTrailingNewline out))
+           else Nothing
+
+cmdLines :: P.ProcessConfig () () () -> IO [String]
+cmdLines p =
+  map B.unpack . B.lines <$> P.readProcessStdout_ p
+
+-- from simple-cmd
+error' :: String -> a
+#if (defined(MIN_VERSION_base) && MIN_VERSION_base(4,9,0))
+error' = errorWithoutStackTrace
+#else
+error' = error
+#endif
+
+tagDistCmd :: Bool -> Bool -> IO ()
+tagDistCmd sdistOnly force = do
+  pkgid <- checkPackage True
+  let tag = pkgidTag pkgid
+  tagHash <- cmdMaybe (git "rev-parse" [tag])
+  if sdistOnly
+    then do
+    when (isNothing tagHash) $
+      error' $ "tag " ++ tag ++ " do not exist"
+    sdist force pkgid
+    else do
+    when (isJust tagHash && not force) $
+      error' $ "tag " ++ tag ++ " exists: use --force to override and move"
+    git_ "tag" $ ["--force" | force] ++ [tag]
+    unless force $ putStrLn tag
+    sdist force pkgid `onException` do
+      putStrLn "Resetting tag"
+      if force
+        then git_ "tag" ["--force", tag, fromJust tagHash]
+        else git_ "tag" ["--delete", tag]
+
+pkgidTag :: PackageIdentifier -> String
+pkgidTag pkgid = "v" ++ packageVersion pkgid
+
+checkPackage :: Bool -> IO PackageIdentifier
+checkPackage checkDiff = do
+  when checkDiff $ do
+    diff <- cmdOut $ git "diff" ["HEAD"]
+    unless (null diff) $ do
+      putStrLn "=== start of uncommitted changes ==="
+      putStrLn diff
+      putStrLn "=== end of uncommitted changes ==="
+  pkgid <- getPackageId
+  checkNameVersionCommitted pkgid
+  checkNotPublished pkgid
+  return pkgid
+  where
+    checkNameVersionCommitted :: PackageIdentifier -> IO ()
+    checkNameVersionCommitted pkgid = do
+      let pkg = packageName pkgid
+          cabalfile = unPackageName pkg <.> "cabal"
+      unlessM (doesFileExist cabalfile) $
+        error' $ ".cabal filename differs from package name: " ++ unPackageName pkg
+      diff <- cmdOut (git "diff" ["-U0", "HEAD", cabalfile])
+      when (any ("+version:" `isPrefixOf`) (lines (map toLower diff))) $
+        error' "Please commit or revert the changed package Version first"
+
+    checkNotPublished :: PackageIdentifier -> IO ()
+    checkNotPublished pkgid = do
+      let published = sdistDir </> showPkgId pkgid <.> ".tar.gz" <.> "published"
+      exists <- doesFileExist published
+      when exists $ error' $ showPkgId pkgid <> " was already published!!"
+      let oldpublished = "dist" </> showPkgId pkgid <.> ".tar.gz" <.> "published"
+      oldExists <- doesFileExist oldpublished
+      when oldExists $ error' $ showPkgId pkgid <> " was already published!!"
+
+sdistDir :: FilePath
+sdistDir = ".hkgr"
+
+sdist :: Bool -> PackageIdentifier -> IO ()
+sdist force pkgid = do
+  let tag = pkgidTag pkgid
+  let target = sdistDir </> showPkgId pkgid <.> ".tar.gz"
+  haveTarget <- doesFileExist target
+  when haveTarget $
+    if force
+    then removeFile target
+    else error' $ target <> " exists already!"
+  cwd <- getCurrentDirectory
+  withTempDirectory "tmp-sdist" $ do
+    git_ "clone" ["-q", "--no-checkout", "..", "."]
+    git_ "checkout" ["-q", tag]
+    whenM (doesFileExist ".gitmodules") $
+      git_ "submodule" ["update", "--init"]
+    cabal_ "check" []
+    mhlint <- findExecutable "hlint"
+    when (isJust mhlint) $ do
+      putStrLn "Running hlint"
+      void $ P.runProcess $ P.proc "hlint" ["--no-summary", "."]
+    let dest = takeDirectory $ cwd </> target
+    unlessM (doesDirectoryExist dest) $
+      createDirectoryIfMissing True dest
+    cabal_ "v2-sdist" ["--output-dir=" ++ dest]
+
+showVersionCmd :: IO ()
+showVersionCmd = do
+  pkgid <- getPackageId
+  putStrLn $ packageVersion pkgid
+
+-- FIXME cabal install creates tarballs now
+uploadCmd :: Bool -> Bool -> IO ()
+uploadCmd publish force = do
+  needProgram "cabal"
+  pkgid <- checkPackage False
+  let file = sdistDir </> showPkgId pkgid <.> ".tar.gz"
+      tag = pkgidTag pkgid
+  exists <- doesFileExist file
+  when (force || not exists) $
+    tagDistCmd False force
+  whenM (null <$> cmdOut (git "branch" ["--contains", "tags/" ++ tag])) $
+    error' $ tag ++ " is no longer on branch: use --force to move it"
+  untagged <- cmdLines $ git "log" ["--pretty=reference", tag ++ "..HEAD"]
+  unless (null untagged) $ do
+    putStrLn "untagged newer commits:"
+    mapM_ putStrLn untagged
+  when publish $ do
+    tagHash <- cmdOut $ git "rev-parse" [tag]
+    branch <- cmdOut $ git "branch" ["--show-current"]
+    mergeable <- gitBool "merge-base" ["--is-ancestor", "HEAD", tagHash]
+    when mergeable $ do
+      putStr "git pushing... "
+      git_ "push" ["--quiet", "origin", tagHash ++ ":" ++ branch]
+      putStrLn "done"
+    git_ "push" ["origin", tag]
+  userpassBS <- getUserPassword
+  void $ P.readProcessInterleaved_ (P.setStdin (P.byteStringInput userpassBS) $ P.proc "cabal" ("upload" : ["--publish" | publish] ++ [file]))
+  putStrLn $ (if publish then "Published at " else "Uploaded to ") ++ "https://hackage.haskell.org/package/" ++ showPkgId pkgid ++ if publish then "" else "/candidate"
+  when publish $
+    createFileLink (takeFileName file) (file <.> "published")
+
+getUserPassword :: IO B.ByteString
+getUserPassword = do
+  cabalConfig <- do
+    home <- getHomeDirectory
+    let cabalConfig = home </> ".cabal/config"
+    exists <- doesFileExist cabalConfig
+    if exists
+      then readFile cabalConfig
+      else do
+      putStrLn $ "Warning: " ++ cabalConfig ++ " not found!"
+      return ""
+  muser <- maybeGetHackage "username" False cabalConfig
+  mpasswd <- maybeGetHackage "password" True cabalConfig
+  return $ B.pack $ unlines $ catMaybes [muser, mpasswd]
+  where
+    maybeGetHackage :: String -> Bool -> String -> IO (Maybe String)
+    maybeGetHackage field hide cabalConfig =
+      if haveCabalConfigField
+        then return Nothing
+        else Just <$> prompt hide ("hackage.haskell.org " ++ field)
+      where
+        haveCabalConfigField :: Bool
+        haveCabalConfigField =
+          any ((field ++ ":") `isPrefixOf`) (lines cabalConfig)
+
+prompt :: Bool -> String -> IO String
+prompt hide s = do
+  putStr $ s ++ ": "
+  inp <- if hide then withoutEcho getLine else getLine
+  when hide $ putChar '\n'
+  if null inp
+    then prompt hide s
+    else return inp
+  where
+    withoutEcho :: IO a -> IO a
+    withoutEcho action =
+      finally (hSetEcho stdin False >> action) (hSetEcho stdin True)
+
+upHaddockCmd :: Bool -> IO ()
+upHaddockCmd publish = do
+  needProgram "cabal"
+  userpassBS <- getUserPassword
+  _out <- P.readProcessInterleaved_ (P.setStdin (P.byteStringInput userpassBS) $ P.proc "cabal" ("upload" : "--documentation" : ["--publish" | publish]))
+
+  pkgid <- getPackageId
+  putStrLn $ (if publish then "Published at " else "Uploaded to ") ++ "https://hackage.haskell.org/package/" ++ showPkgId pkgid ++ if publish then "" else "/candidate"
+
+cabal_ :: String -> [String] -> IO ()
+cabal_ c args =
+  cmd_ "cabal" (c:args)
+
+withTempDirectory :: FilePath -> IO a -> IO a
+withTempDirectory dir run =
+  bracket_ (createDirectory dir) (removeDirectoryRecursive dir) $
+  withCurrentDirectory dir run
+
+-- #if (defined(MIN_VERSION_simple_cmd) && MIN_VERSION_simple_cmd(0,2,1))
+-- #else
+needProgram :: String -> IO ()
+needProgram prog = do
+  mx <- findExecutable prog
+  unless (isJust mx) $ error' $ "program needs " ++ prog
+-- #endif
+
+-- FIXME warning if upstream template changed (keep a versioned template copy)
+-- FIXME add default templates dir
+-- FIXME --license
+newCmd :: Maybe String -> IO ()
+newCmd mproject = do
+  needProgram "cabal"
+  name <- case mproject of
+    Just ns -> return ns
+    Nothing -> do
+      files <- listDirectory "."
+      if null files then takeFileName <$> getCurrentDirectory
+        else do
+        -- filter out dirs
+        dirs <- filterM doesDirectoryExist files
+        if dirs == files then error' "Could not guess name"
+          else
+          case filter ("cabal" `isExtensionOf`) $ files \\ dirs of
+            [] -> takeFileName <$> getCurrentDirectory
+            [cbl] -> return $ takeBaseName cbl
+            _ -> error' "More than one .cabal file found!"
+  when (isJust mproject) $ createDirectory name >> setCurrentDirectory name
+  mcabal <- checkForCabalFile
+  case mcabal of
+    Nothing -> do
+      let setupFile = "Setup.hs"
+      origsetup <- doesFileExist setupFile
+      cabal_ "init" ["--quiet", "--no-comments", "--non-interactive", "--is-libandexe", "--cabal-version=1.18", "--license=BSD3", "--package-name=" ++ name, "--version=0.1.0", "--dependency=base<5", "--source-dir=src"]
+      whenJustM (cmdMaybe $ P.proc "find" ["-name", "Main.hs"]) $ \ file -> do
+        sed ["1s/^module Main where/-- SPDX-License-Identifier: BSD-3-Clause\\n\\nmodule Main (main) where/"] file
+        unless (file == "src/Main.hs") $ renameFile file "src/Main.hs"
+      whenM (doesFileExist "CHANGELOG.md") $
+        renameFile "CHANGELOG.md" "ChangeLog.md"
+      unlessM (doesFileExist "README.md") $
+        writeFile "README.md" $ "# " ++ name ++ "\n"
+      unless origsetup $ do
+        setup <- doesFileExist setupFile
+        when setup $ removeFile setupFile
+      setupCabalTemplate name
+    Just cblName ->
+      when (cblName /= name) $
+      putStrLn $ "Warning: " ++ cblName ++ " different to " ++ name
+  haveStackCfg <- doesFileExist "stack.yaml"
+  mstack <- findExecutable "stack"
+  -- FIXME add stack.yaml template too
+  when (not haveStackCfg && isJust mstack) $ do
+    cmd_ "stack" ["init", "--verbosity", "warn", "--resolver", "lts-16"]
+    sed ["/^#/d", "/^$/d"] "stack.yaml"
+  haveGit <- doesDirectoryExist ".git"
+  unless haveGit $ do
+    git_ "init" ["-q"]
+    git_ "add" [name <.> "cabal"]
+  where
+    checkForCabalFile :: IO (Maybe String)
+    checkForCabalFile = do
+      files <- listDirectory "."
+      case filter ("cabal" `isExtensionOf`) files of
+        [] -> return Nothing
+        [cbl] -> return $ Just (takeBaseName cbl)
+        _ -> error' "More than one .cabal file found!"
+
+    sed :: [String] -> FilePath -> IO ()
+    sed [] _ = error' "sed given no script"
+    sed args file =
+      cmd_ "sed" $ ["-i", "-e"] ++ intersperse "-e" args ++ [file]
+
+    setupCabalTemplate :: String -> IO ()
+    setupCabalTemplate name = do
+      userTemplate <- getUserConfigFile "hkgr" "template.cabal"
+      unlessM (doesFileExist userTemplate) $ do
+        origTemplate <- getDataFileName "template.cabal.tmpl"
+        createDirectoryIfMissing True $ takeDirectory userTemplate
+        -- FIXME put a copy of the current template there too for reference
+        copyFile origTemplate userTemplate
+        username <- cmdOut $ git "config" ["--global", "user.name"]
+        replaceHolder "NAME" username userTemplate
+        usermail <-
+          fromMaybeM (cmdOut $ git "config" ["--global", "user.email"]) $
+            cmdMaybe $ git "config" ["--global", "github.email"]
+        replaceHolder "EMAIL" usermail userTemplate
+        githubuser <- cmdOut $ git "config" ["--global", "github.user"]
+        replaceHolder "USER" githubuser userTemplate
+        putStrLn $ userTemplate ++ " set up"
+      copyFile userTemplate $ name <.> "cabal"
+      replaceHolder "PROJECT" name $ name <.> "cabal"
+      replaceHolder "PROJECT_" (map underscore name) $ name <.> "cabal"
+      replaceHolder "SUMMARY" (name ++ " project") $ name <.> "cabal"
+      year <- cmdOut (P.proc "date" ["+%Y"])
+      replaceHolder "YEAR" year $ name <.> "cabal"
+      let modulePath = "src/MyLib.hs"
+      unlessM (doesFileExist modulePath) $ do
+        createDirectoryIfMissing True $ takeDirectory modulePath
+        writeFile modulePath "-- SPDX-License-Identifier: BSD-3-Clause\n\nmodule MyLib where\n"
+        -- sed ["-i", "1s/^/-- SPDX-License-Identifier: BSD-3-Clause\n\n/"] modulePath
+      where
+        replaceHolder lbl val file =
+          sed ["s/@" ++ lbl ++ "@/" ++ val ++ "/"] file
+
+        underscore '-' = '_'
+        underscore c = c
+
+#if !MIN_VERSION_filepath(1,4,2)
+    isExtensionOf :: String -> FilePath -> Bool
+    isExtensionOf ext@('.':_) = isSuffixOf ext . takeExtensions
+    isExtensionOf ext         = isSuffixOf ('.':ext) . takeExtensions
+#endif
+
+#if !MIN_VERSION_extra(1,6,15)
+fromMaybeM :: Monad m => m a -> m (Maybe a) -> m a
+fromMaybeM n = maybeM n return
+#endif
