diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.markdown
@@ -0,0 +1,7 @@
+# Change log
+
+GitHub Release uses [Semantic Versioning][].
+The change log is available through the [releases on GitHub][].
+
+[Semantic Versioning]: http://semver.org/spec/v2.0.0.html
+[releases on GitHub]: https://github.com/tfausak/github-release/releases
diff --git a/LICENSE.markdown b/LICENSE.markdown
new file mode 100644
--- /dev/null
+++ b/LICENSE.markdown
@@ -0,0 +1,23 @@
+# [The MIT License (MIT)][]
+
+Copyright (c) 2016 Taylor Fausak
+
+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.
+
+[The MIT License (MIT)]: http://opensource.org/licenses/MIT
diff --git a/README.markdown b/README.markdown
new file mode 100644
--- /dev/null
+++ b/README.markdown
@@ -0,0 +1,25 @@
+# [GitHub Release][]
+
+[![Version badge][]][version]
+[![Build badge][]][build]
+
+GitHub Release is a command-line utility for uploading files to GitHub
+releases.
+
+``` sh
+github-release upload \
+  --token '...' \
+  --owner 'someone' \
+  --repo 'something' \
+  --tag '1.2.3' \
+  --file 'path/to/example.tgz' \
+  --name 'example-1.2.3.tgz'
+```
+
+Inspired by <https://github.com/aktau/github-release>.
+
+[GitHub Release]: https://github.com/tfausak/github-release
+[Version badge]: https://www.stackage.org/package/github-release/badge/nightly?label=version
+[version]: https://www.stackage.org/package/github-release
+[Build badge]: https://travis-ci.org/tfausak/github-release.svg
+[build]: https://travis-ci.org/tfausak/github-release
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,4 @@
+import qualified Distribution.Simple
+
+main :: IO ()
+main = Distribution.Simple.defaultMain
diff --git a/executable/Main.hs b/executable/Main.hs
new file mode 100644
--- /dev/null
+++ b/executable/Main.hs
@@ -0,0 +1,3 @@
+module Main (module GitHubRelease) where
+
+import GitHubRelease (main)
diff --git a/github-release.cabal b/github-release.cabal
new file mode 100644
--- /dev/null
+++ b/github-release.cabal
@@ -0,0 +1,53 @@
+name: github-release
+version: 0.1.0
+cabal-version: >=1.10
+build-type: Simple
+license: MIT
+license-file: LICENSE.markdown
+maintainer: Taylor Fausak
+homepage: https://github.com/tfausak/github-release#readme
+bug-reports: https://github.com/tfausak/github-release/issues
+synopsis: Upload files to GitHub releases.
+description:
+    GitHub Release is a command-line utility for uploading files to GitHub releases.
+category: Utility
+extra-source-files:
+    CHANGELOG.markdown
+    package.yaml
+    README.markdown
+    stack.yaml
+
+source-repository head
+    type: git
+    location: https://github.com/tfausak/github-release
+
+library
+    exposed-modules:
+        GitHubRelease
+    build-depends:
+        aeson >=0.9.0.1 && <0.10,
+        base >=4.8.2.0 && <4.9,
+        bytestring >=0.10.6.0 && <0.11,
+        http-client >=0.4.28 && <0.5,
+        http-client-tls >=0.2.4 && <0.3,
+        http-types ==0.9.*,
+        mime-types >=0.1.0.7 && <0.2,
+        optparse-generic >=1.1.0 && <1.2,
+        text >=1.2.2.1 && <1.3,
+        unordered-containers >=0.2.5.1 && <0.3,
+        uri-templater >=0.2.0.0 && <0.3
+    default-language: Haskell2010
+    hs-source-dirs: library
+    other-modules:
+        Paths_github_release
+    ghc-options: -Wall
+
+executable github-release
+    main-is: Main.hs
+    build-depends:
+        base >=4.8.2.0 && <4.9,
+        github-release >=0.1.0 && <0.2
+    default-language: Haskell2010
+    hs-source-dirs: executable
+    ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N
+
diff --git a/library/GitHubRelease.hs b/library/GitHubRelease.hs
new file mode 100644
--- /dev/null
+++ b/library/GitHubRelease.hs
@@ -0,0 +1,123 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeOperators #-}
+
+module GitHubRelease where
+
+import qualified Data.Aeson as Aeson
+import qualified Data.ByteString.Char8 as BS8
+import qualified Data.ByteString.Lazy as BSL
+import qualified Data.HashMap.Strict as HashMap
+import qualified Data.Text as Text
+import qualified Data.Version as Version
+import qualified GHC.Generics as Generics
+import qualified Network.HTTP.Client as Client
+import qualified Network.HTTP.Client.TLS as TLS
+import qualified Network.HTTP.Types as HTTP
+import qualified Network.Mime as MIME
+import qualified Network.URI.Template as Template
+import qualified Network.URI.Template.Types as Template
+import qualified Options.Generic as Options
+import qualified Paths_github_release as This
+import qualified System.IO as IO
+import qualified Text.Printf as Printf
+
+data Command
+    = Upload
+        { file :: FilePath Options.<?>
+            "The path to the local file to upload."
+        , name :: String Options.<?>
+            "The name to give the file on the release."
+        , owner :: String Options.<?>
+            "The GitHub owner, either a user or organization."
+        , repo :: String Options.<?>
+            "The GitHub repository name."
+        , tag :: String Options.<?>
+            "The tag name."
+        , token :: String Options.<?>
+            "Your OAuth2 token."
+        }
+    | Version
+    deriving (Generics.Generic, Show)
+
+instance Options.ParseRecord Command
+
+main :: IO ()
+main = do
+    command <- Options.getRecord (Text.pack "Upload a file to a GitHub release.")
+    runCommand command
+
+runCommand :: Command -> IO ()
+runCommand command = case command of
+    Upload aName anOwner aFile aRepo aTag aToken -> upload
+        (Options.unHelpful aToken)
+        (Options.unHelpful anOwner)
+        (Options.unHelpful aRepo)
+        (Options.unHelpful aTag)
+        (Options.unHelpful aFile)
+        (Options.unHelpful aName)
+    Version -> putStrLn versionString
+
+upload :: String -> String -> String -> String -> FilePath -> String -> IO ()
+upload aToken anOwner aRepo aTag aFile aName = do
+    manager <- Client.newManager TLS.tlsManagerSettings
+    uploadUrl <- getUploadUrl manager anOwner aRepo aTag
+    response <- uploadFile manager uploadUrl aToken aFile aName
+    case HTTP.statusCode (Client.responseStatus response) of
+        201 -> pure ()
+        _ -> do
+            IO.hPrint IO.stderr response
+            BSL.hPutStr IO.stderr (Client.responseBody response)
+            fail "Failed to upload file to release!"
+
+getUploadUrl :: Client.Manager -> String -> String -> String -> IO Template.UriTemplate
+getUploadUrl manager anOwner aRepo aTag = do
+    (Right json) <- getTag manager anOwner aRepo aTag
+    let (Just (Aeson.String text)) = HashMap.lookup (Text.pack "upload_url") json
+    let uploadUrl = Text.unpack text
+    let (Right template) = Template.parseTemplate uploadUrl
+    pure template
+
+getTag :: Client.Manager -> String -> String -> String -> IO (Either String Aeson.Object)
+getTag manager anOwner aRepo aTag = do
+    let format = "https://api.github.com/repos/%s/%s/releases/tags/%s"
+    let url = Printf.printf format anOwner aRepo aTag
+    initialRequest <- Client.parseUrl url
+    let request = initialRequest { Client.requestHeaders = [userAgentHeader] }
+    response <- Client.httpLbs request manager
+    let body = Client.responseBody response
+    let json = Aeson.eitherDecode body
+    return json
+
+userAgentHeader :: HTTP.Header
+userAgentHeader = (HTTP.hUserAgent, BS8.pack userAgent)
+
+userAgent :: String
+userAgent = Printf.printf "%s/%s-%s" "tfausak" "github-release" versionString
+
+versionString :: String
+versionString = Version.showVersion This.version
+
+uploadFile :: Client.Manager -> Template.UriTemplate -> String -> FilePath -> String -> IO (Client.Response BSL.ByteString)
+uploadFile manager template aToken aFile aName = do
+    contents <- BSL.readFile aFile
+    let body = Client.RequestBodyLBS contents
+    uploadBody manager template aToken body aName
+
+uploadBody :: Client.Manager -> Template.UriTemplate -> String -> Client.RequestBody -> String -> IO (Client.Response BSL.ByteString)
+uploadBody manager template aToken body aName = do
+    let url = Template.render template
+            [ ("name", Template.WrappedValue (Template.Single aName))
+            ]
+    initialRequest <- Client.parseUrl url
+    let request = initialRequest
+            { Client.method = BS8.pack "POST"
+            , Client.requestBody = body
+            , Client.requestHeaders =
+                [ (HTTP.hAuthorization, BS8.pack (Printf.printf "token %s" aToken))
+                , (HTTP.hContentType, MIME.defaultMimeLookup (Text.pack aName))
+                , userAgentHeader
+                ]
+            }
+    Client.httpLbs request manager
diff --git a/package.yaml b/package.yaml
new file mode 100644
--- /dev/null
+++ b/package.yaml
@@ -0,0 +1,42 @@
+category: Utility
+description: GitHub Release is a command-line utility for uploading files to GitHub
+  releases.
+executables:
+  github-release:
+    dependencies:
+    - base
+    - github-release
+    ghc-options:
+    - -threaded
+    - -rtsopts
+    - -with-rtsopts=-N
+    main: Main.hs
+    source-dirs: executable
+extra-source-files:
+- CHANGELOG.markdown
+- package.yaml
+- README.markdown
+- stack.yaml
+ghc-options: -Wall
+github: tfausak/github-release
+library:
+  dependencies:
+  - aeson
+  - base
+  - bytestring
+  - http-client
+  - http-client-tls
+  - http-types
+  - mime-types
+  - optparse-generic
+  - text
+  - unordered-containers
+  - uri-templater
+  other-modules: Paths_github_release
+  source-dirs: library
+license: MIT
+license-file: LICENSE.markdown
+maintainer: Taylor Fausak
+name: github-release
+synopsis: Upload files to GitHub releases.
+version: '0.1.0'
diff --git a/stack.yaml b/stack.yaml
new file mode 100644
--- /dev/null
+++ b/stack.yaml
@@ -0,0 +1,3 @@
+extra-deps:
+- optparse-generic-1.1.0
+resolver: lts-5.15
