diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/Shakefile.hs b/Shakefile.hs
new file mode 100644
--- /dev/null
+++ b/Shakefile.hs
@@ -0,0 +1,33 @@
+#!/usr/bin/env stack
+{- stack
+    runghc
+    --package shakers
+ -}
+
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+-- | Shake makefile for project.
+--
+import Development.Shakers
+
+-- | Main entry point.
+--
+main :: IO ()
+main = shakeMain $ do
+  let pats =
+        [ "stack.yaml"
+        , "Shakefile.hs"
+        , "main//*.hs"
+        , "src//*.hs"
+        ]
+
+  -- | Imported rules.
+  --
+  hsRules "."
+  stackRules "." pats
+  cabalRules "." "skylark-client.cabal"
+
+  -- | Default things to run.
+  --
+  want [ fakeFile "lint", fakeFile "format" ]
diff --git a/main/skylark-client.hs b/main/skylark-client.hs
new file mode 100644
--- /dev/null
+++ b/main/skylark-client.hs
@@ -0,0 +1,21 @@
+{-# LANGUAGE DeriveGeneric     #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+import Data.Conduit.Binary
+import Network.Skylark.Client
+import Options.Generic
+import Preamble
+import System.IO
+
+data Args = Args
+  { url    :: String
+  , device :: ByteString
+  } deriving (Show, Generic)
+
+instance ParseRecord Args
+
+main :: IO ()
+main = do
+  args <- getRecord "NTRIP Client"
+  runSkylark (url args) (device args) (sourceHandle stdin) (sinkHandle stdout)
diff --git a/skylark-client.cabal b/skylark-client.cabal
new file mode 100644
--- /dev/null
+++ b/skylark-client.cabal
@@ -0,0 +1,44 @@
+name:                  skylark-client
+version:               0.1.0
+synopsis:              Skylark client.
+description:           Skylark network client.
+homepage:              https://github.com/githubuser/skylark-client#readme
+license:               BSD3
+author:                Swift Navigation Inc.
+maintainer:            Mark Fine <dev@swiftnav.com>
+copyright:             Copyright (C) 2017 Swift Navigation, Inc.
+category:              Network
+build-type:            Simple
+cabal-version:         >= 1.22
+
+library
+  hs-source-dirs:      src
+  exposed-modules:     Network.Skylark.Client
+  ghc-options:         -Wall
+  default-language:    Haskell2010
+  build-depends:       async
+                     , base == 4.8.*
+                     , conduit
+                     , conduit-extra
+                     , http-conduit
+                     , http-types
+                     , preamble
+                     , resourcet
+
+executable skylark-client
+  hs-source-dirs:      main
+  main-is:             skylark-client.hs
+  ghc-options:         -threaded -rtsopts -with-rtsopts=-N -Wall -O2
+  default-language:    Haskell2010
+  build-depends:       base
+                     , conduit-extra
+                     , optparse-generic
+                     , preamble
+                     , skylark-client
+
+executable shake-skylark-client
+  main-is:             Shakefile.hs
+  ghc-options:         -threaded -rtsopts -with-rtsopts=-N -Wall -O2
+  default-language:    Haskell2010
+  build-depends:       base == 4.8.*
+                     , shakers
diff --git a/src/Network/Skylark/Client.hs b/src/Network/Skylark/Client.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Skylark/Client.hs
@@ -0,0 +1,62 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Network.Skylark.Client
+  ( runSkylark
+  ) where
+
+import Control.Concurrent.Async
+import Control.Monad.Trans.Resource
+import Data.Conduit
+import Network.HTTP.Conduit
+import Network.HTTP.Types
+import Network.HTTP.Types.Header
+import Preamble
+
+{-# ANN module ("HLint: ignore Redundant flip"::String) #-}
+
+-- | Device-Uid Header
+--
+hDevice :: HeaderName
+hDevice = "Device-Uid"
+
+-- | SBPv2 Content-Type
+--
+sbpContentType :: ByteString
+sbpContentType = "application/vnd.swiftnav.broker.v1+sbp2"
+
+-- | Download data from Skylark to sink.
+--
+download :: MonadIO m => ByteString -> Manager -> Request -> Sink ByteString (ResourceT IO) () -> m ()
+download device manager request sink =
+  liftIO $ runResourceT $ do
+    response <- flip http manager request
+      { method = methodGet
+      , requestHeaders =
+          [ (hAccept, sbpContentType)
+          , (hDevice, device)
+          , (hPragma, "proxy")
+          ]
+      }
+    responseBody response $$+- sink
+
+-- | Upload data to Skylark from source.
+--
+upload :: MonadIO m => ByteString -> Manager -> Request -> Source (ResourceT IO) ByteString -> m ()
+upload device manager request source =
+  void $ flip httpLbs manager request
+    { method      = methodPut
+    , requestHeaders =
+        [ (hContentType, sbpContentType)
+        , (hDevice, device)
+        ]
+    , requestBody = requestBodySourceChunked source
+    }
+
+-- | Run Skylark client.
+--
+runSkylark :: (MonadIO m, MonadThrow m) => String -> ByteString -> Source (ResourceT IO) ByteString -> Sink ByteString (ResourceT IO) () -> m ()
+runSkylark url device source sink = do
+  manager <- liftIO $ newManager tlsManagerSettings
+  request <- parseRequest url
+  liftIO $ void $ concurrently (upload device manager request source) (download device manager request sink)
