diff --git a/seqloc-datafiles.cabal b/seqloc-datafiles.cabal
--- a/seqloc-datafiles.cabal
+++ b/seqloc-datafiles.cabal
@@ -1,5 +1,5 @@
 Name:                seqloc-datafiles
-Version:             0.2.2.1
+Version:             0.2.2.2
 Cabal-Version:       >= 1.6
 Synopsis:            Read and write BED and GTF format genome annotations
 Description:         Read and write BED and GTF format genome annotations
@@ -31,6 +31,16 @@
 
 Executable gtf-to-bed
   Main-is:             GtfToBed.hs
+  Other-modules:       Bio.SeqLoc.GTF, Bio.SeqLoc.Bed, Bio.SeqLoc.ZeptoUtils
+  Build-depends:       base >= 4.2 && < 5, bytestring,
+                       attoparsec >= 0.8.5, hashable, unordered-containers,
+                       iteratee >= 0.8.1, seqloc >= 0.3.1, biocore >= 0.2, transformers, monads-tf 
+  Hs-Source-Dirs:      src
+  Ghc-options:         -Wall -rtsopts
+  C-Sources:           src/rtsopts.c
+
+Executable bed-to-gtf
+  Main-is:             BedToGtf.hs
   Other-modules:       Bio.SeqLoc.GTF, Bio.SeqLoc.Bed, Bio.SeqLoc.ZeptoUtils
   Build-depends:       base >= 4.2 && < 5, bytestring,
                        attoparsec >= 0.8.5, hashable, unordered-containers,
diff --git a/src/BedToGtf.hs b/src/BedToGtf.hs
new file mode 100644
--- /dev/null
+++ b/src/BedToGtf.hs
@@ -0,0 +1,72 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Main
+       where
+
+import Control.Applicative
+import Control.Monad.Reader
+import qualified Data.ByteString.Char8 as BS
+import Data.Maybe
+
+import System.Console.GetOpt
+import System.Environment
+import System.IO
+
+import Bio.SeqLoc.Bed
+import Bio.SeqLoc.GTF
+import qualified Bio.SeqLoc.Location as Loc
+import Bio.SeqLoc.Strand
+import Bio.SeqLoc.Transcript
+
+main :: IO ()
+main = getArgs >>= handleOpt . getOpt RequireOrder optDescrs
+    where handleOpt (_,    _,         errs@(_:_)) = usage (unlines errs)
+          handleOpt (args, [bed], []) = either usage (doBedToGtf bed) $ argsToConf args
+          handleOpt (_,    _,     []) = usage "Specify exactly one GTF file"
+          usage errs = do prog <- getProgName
+                          hPutStr stderr $ usageInfo prog optDescrs
+                          hPutStrLn stderr errs
+
+doBedToGtf :: FilePath -> Conf -> IO ()
+doBedToGtf bed conf = readBedTranscripts bed >>= writeTranscriptsOut . map adjustStop
+  where writeTranscriptsOut trxs = withOutHandle conf $ \h -> 
+          mapM_ (BS.hPutStr h . transcriptToGtf "bed-to-gtf") trxs
+        adjustStop = if confStopIncluded conf then removeDoubleStop else id
+        removeDoubleStop trx = trx { cds = cds trx >>= shortenLoc }
+        shortenLoc l = Loc.clocOutof (Loc.fromBoundsStrand 0 end Plus) l
+          where end = max 0 $ Loc.length l - 4
+
+withOutHandle :: Conf -> (Handle -> IO a) -> IO a
+withOutHandle conf m = maybe (m stdout) (\outname -> withFile outname WriteMode m) $ confOutput conf
+
+data Conf = Conf { confOutput :: !(Maybe FilePath) 
+                 , confBadTranscripts :: !(Maybe FilePath)
+                 , confStopIncluded :: !Bool
+                 } deriving (Show)
+
+data Arg = ArgOutput { unArgOutput :: !String }
+         | ArgBadTranscripts { unArgBadTranscripts :: !String }
+         | ArgStopIncluded
+         deriving (Show, Read, Eq, Ord)
+
+argOutput :: Arg -> Maybe String
+argOutput (ArgOutput del) = Just del
+argOutput _ = Nothing
+
+argBadTranscripts :: Arg -> Maybe String
+argBadTranscripts (ArgBadTranscripts bad) = Just bad
+argBadTranscripts _ = Nothing
+
+optDescrs :: [OptDescr Arg]
+optDescrs = [ Option ['o'] ["output"]        (ReqArg ArgOutput "OUTFILE") "Output filename"
+            , Option ['b'] ["bad-transcripts"] (ReqArg ArgBadTranscripts "BADFILE") "Write bad transcript list to file"
+            , Option ['s'] ["stop-included"] (NoArg ArgStopIncluded)      "GTF file includes stop codon in CDS"
+            ]
+
+argsToConf :: [Arg] -> Either String Conf
+argsToConf = runReaderT conf
+    where conf = Conf <$> 
+                 findOutput <*>
+                 findBadTranscripts <*>
+                 (ReaderT $ return . elem ArgStopIncluded)
+          findOutput = ReaderT $ return . listToMaybe . mapMaybe argOutput
+          findBadTranscripts = ReaderT $ return . listToMaybe . mapMaybe argBadTranscripts
