diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,17 @@
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,6 @@
+module Main(main)
+
+import Distribution.Simple
+
+main :: IO ()
+main = defaultMain
diff --git a/decode-utf8.cabal b/decode-utf8.cabal
new file mode 100644
--- /dev/null
+++ b/decode-utf8.cabal
@@ -0,0 +1,26 @@
+name: decode-utf8
+version: 1.0
+category: Text
+synopsis: Decode a UTF-8 byte stream on standard input
+license: MIT
+license-file: LICENSE
+cabal-version: >= 1.8.0.2
+build-type: Simple
+author: Joe Leslie-Hurd <joe@gilith.com>
+maintainer: Joe Leslie-Hurd <joe@gilith.com>
+description:
+  This package implements a simple utility to decode a UTF-8 byte stream
+  fed in on standard input, displaying the valid Unicode characters read
+  and the invalid bytes.
+
+executable decode-utf8
+  build-depends:
+    base >= 4.0 && < 5.0,
+    opentheory-unicode >= 1.0 && < 2.0,
+    api-opentheory-unicode >= 1.0 && < 2.0
+
+  hs-source-dirs: src
+
+  ghc-options: -Wall
+
+  main-is: Main.hs
diff --git a/src/Main.hs b/src/Main.hs
new file mode 100644
--- /dev/null
+++ b/src/Main.hs
@@ -0,0 +1,31 @@
+{- |
+module: Main
+description: Decode a UTF-8 byte stream on standard input
+license: MIT
+
+maintainer: Joe Leslie-Hurd <joe@gilith.com>
+stability: provisional
+portability: portable
+-}
+module Main
+  ( main )
+where
+
+import qualified Data.Word as Word
+
+import OpenTheory.Unicode
+import qualified Unicode
+
+info :: Either Word.Word8 Unicode -> String
+info (Left b) = "invalid byte " ++ show b
+info (Right c) =
+    "valid unicode character " ++ show (unUnicode c) ++ " " ++
+    Unicode.toString c
+
+display :: Either Word.Word8 Unicode -> IO ()
+display c = putStrLn (info c)
+
+main :: IO ()
+main =
+    do cs <- Unicode.decode
+       mapM_ display cs
