diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,6 +1,10 @@
 
 # `gemmula-altera` Change Log
 
+## 2.1.0 (January 19, 2024)
+* Reimplement the stdin functionality of `gemalter`.
+* Default HTML title for `gemalter` inputs don't include the file extension now.
+
 ## 2.0.0 (January 18, 2024)
 * Add `gemalter` executable.
 * Add `Text.Gemini.Web.getTitle` function.
diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -31,8 +31,8 @@
 
 import Paths_gemmula_altera (version)
 import Data.Version (showVersion)
-import Data.Text.IO (readFile, writeFile)
-import Prelude hiding (readFile, writeFile)
+import Data.Text.IO (readFile, writeFile, putStr)
+import Prelude hiding (readFile, writeFile, putStr)
 
 import Text.Gemini (GemDocument)
 import qualified Text.Gemini as G
@@ -40,20 +40,15 @@
 import qualified Text.Gemini.Web as W
 
 
--- | Process the input files according to the command and write them.
+-- | Process the input files (or piped input) according to the command and write them.
 run :: Opts -> IO ()
-run opt = case mode opt of
-    Markdown {} -> writeFiles =<< processFiles opt (markdown opt) "md"
-    Web {} -> writeFiles =<< processFiles opt (web opt) "html"
-
--- | Process the piped input and write it.
--- Doesn't have any advanced options.
-pipeRun :: PipeOpts -> IO ()
-pipeRun opt = case pipeMode opt of
-    PipeMarkdown {} -> putStr =<< processPipe M.encode
-    PipeWeb {} -> putStr =<< processPipe W.encode
-    where processPipe :: (GemDocument -> Text) -> IO String
-          processPipe convert = T.unpack . convert . G.decode . T.pack <$> getContents
+run opt@Opts { mode }
+    | null $ inputs mode = hReady stdin >>= bool (errorWithoutStackTrace "No inputs found.")
+                                   (putStr =<< processStdin opt convert)
+    | otherwise = writeFiles =<< processFiles opt convert ext
+    where (convert, ext) = case mode of
+                               Markdown {} -> (markdown opt, "md")
+                               Web {} -> (web opt, "html")
 
 
 -- | Convert Gemtext to Markdown.
@@ -77,7 +72,7 @@
 web :: Opts -> FilePath -> GemDocument -> IO Text
 web (Opts { mode, webify }) path doc
     | body mode = fmap (W.encode . map W.rewriteLink) . bool return (mapM W.webifyLink) webify $ doc
-    | otherwise = let title = fromMaybe (T.pack $ takeFileName path) $ W.getTitle doc
+    | otherwise = let title = fromMaybe (T.pack $ takeBaseName path) $ W.getTitle doc
                       defaultTemplate = T.unlines
                           [ "<!DOCTYPE html>"
                           , "<html lang=\"en\">"
@@ -151,7 +146,14 @@
           recurse root path = let proc p = process (root </> last (splitDirectories path)) (path </> p)
                                in fmap concat . mapM proc =<< listDirectory path
 
+processStdin :: Opts -> (FilePath -> GemDocument -> IO Text) -> IO Text
+processStdin (Opts { mode }) convert = let
+    insertPathVars t = case mode of
+        Web {} -> insertVars t <$> mapM getVar (filevars mode)
+        _ -> return t
+    in insertPathVars =<< convert "gemalter" . G.decode . T.pack =<< getContents
 
+
 -- | Parse the variable option and make it relative to the 'FilePath'.
 pathVar :: FilePath -> Text -> IO (Text, Text)
 pathVar path text = (\(name, val) ->
@@ -201,7 +203,7 @@
        <> help "Rewrite gemini links as http if they can be reached" )
     <*> hsubparser
         ( command "md" (info (Markdown
-                  <$> some (argument str (metavar "FILES"))
+                  <$> many (argument str (metavar "FILES"))
               ) (progDesc "Convert Gemtext files to Markdown."))
        <> command "web" (info (Web
                   <$> switch
@@ -222,27 +224,12 @@
                             \ the given path, in the form of \"name=value\", where \
                             \ value must be a path relative to the output root"
                      <> metavar "VAR" ))
-                  <*> some (argument str (metavar "FILES"))
+                  <*> many (argument str (metavar "FILES"))
               ) (progDesc "Convert Gemtext files to HTML.")) )
 
--- | Options to use if there's a piped input.
-newtype PipeOpts = PipeOpts { pipeMode :: PipeCommand } deriving (Show, Eq)
-data PipeCommand = PipeMarkdown | PipeWeb deriving (Show, Eq)
-
-pipeOpts :: Parser PipeOpts
-pipeOpts = PipeOpts
-    <$> hsubparser
-        ( command "md" (info (pure PipeMarkdown)
-              (progDesc "Convert Gemtext files to Markdown."))
-       <> command "web" (info (pure PipeWeb)
-              (progDesc "Convert Gemtext files to HTML.")) )
-
 main :: IO ()
-main = let parser p = info (p <**> helper <**> simpleVersioner ("v" <> showVersion version))
-                          ( fullDesc
-                         <> progDesc "Convert Gemtext to Markdown and HTML using gemmula library."
-                         <> header "gemalter - a tiny command line helper for converting Gemini capsules")
-       in hReady stdin >>= \case
-              False -> run =<< execParser (parser opts)
-              True -> pipeRun =<< execParser (parser pipeOpts)
+main = run =<< execParser (info (opts <**> helper <**> simpleVersioner ("v" <> showVersion version))
+                              ( fullDesc
+                             <> progDesc "Convert Gemtext to Markdown and HTML using gemmula library."
+                             <> header "gemalter - a tiny command line helper for converting Gemini capsules"))
 
diff --git a/gemmula-altera.cabal b/gemmula-altera.cabal
--- a/gemmula-altera.cabal
+++ b/gemmula-altera.cabal
@@ -1,5 +1,5 @@
 name:               gemmula-altera
-version:            2.0.0
+version:            2.1.0
 synopsis:           A tiny Gemtext converter for gemmula
 description:        gemmula-altera is a tiny Gemtext converter, built on gemmula library.
                     It provides simple encodings for Markdown and HTML, plus a tiny
