diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Revision history for ollama-haskell
+
+## 0.1.0.0 -- YYYY-mm-dd
+
+* First version. Released on an unsuspecting world.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2024 Tushar Adhatrao
+
+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/ollama-haskell.cabal b/ollama-haskell.cabal
new file mode 100644
--- /dev/null
+++ b/ollama-haskell.cabal
@@ -0,0 +1,71 @@
+cabal-version:      3.4
+name:               ollama-haskell
+version:            0.1.0.0
+synopsis:           Ollama Haskell library
+-- description:
+license:            MIT
+license-file:       LICENSE
+author:             Tushar Adhatrao
+maintainer:         tusharadhatrao@gmail.com
+description:        Haskell bindings for Ollama.
+-- copyright:
+category:           Data
+build-type:         Simple
+extra-doc-files:    CHANGELOG.md
+-- extra-source-files:
+Homepage:            https://github.com/tusharad/ollama-haskell
+Bug-reports:         https://github.com/tusharad/ollama-haskell/issues
+extra-source-files:  CHANGELOG.md
+
+Source-repository head
+  type:     git
+  location: https://github.com/tusharad/ollama-haskell
+
+common warnings
+    ghc-options: -Wall
+
+library
+    import:           warnings
+    exposed-modules:  Ollama
+                    , Data.Ollama.Chat
+                    , Data.Ollama.Copy
+                    , Data.Ollama.Common.Types
+                    , Data.Ollama.Common.Utils
+                    , Data.Ollama.Create
+                    , Data.Ollama.Delete
+                    , Data.Ollama.Embeddings
+                    , Data.Ollama.Generate
+                    , Data.Ollama.List
+                    , Data.Ollama.Ps
+                    , Data.Ollama.Pull
+                    , Data.Ollama.Push
+                    , Data.Ollama.Show
+                    , Lib
+    -- other-modules:
+    -- other-extensions:
+    build-depends:    base ^>=4.18
+                    , aeson >= 2.2.1 && < 2.3
+                    , bytestring >= 0.11.5 && < 0.12
+                    , text >= 2.0.2 && < 2.1
+                    , time >= 1.12.2 && < 1.13
+                    , http-client >= 0.7.17 && < 0.8
+                    , http-types >= 0.12.4 && < 0.13
+
+    hs-source-dirs:   src
+    default-language: GHC2021
+
+test-suite ollama-haskell-test
+    import:           warnings
+    default-language: GHC2021
+    -- other-modules:
+    -- other-extensions:
+    type:             exitcode-stdio-1.0
+    hs-source-dirs:   test
+    main-is:          Main.hs
+    build-depends:
+        base ^>=4.18,
+        tasty,
+        tasty-hunit,
+        text,
+        silently,
+        ollama-haskell
diff --git a/src/Data/Ollama/Chat.hs b/src/Data/Ollama/Chat.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Ollama/Chat.hs
@@ -0,0 +1,212 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Data.Ollama.Chat
+  ( -- * Chat APIs
+    chat
+  , Message (..)
+  , Role (..)
+  , defaultChatOps
+  , ChatOps (..)
+  , ChatResponse (..)
+  ) where
+
+import Data.Aeson
+import Data.ByteString.Char8 qualified as BS
+import Data.ByteString.Lazy.Char8 qualified as BSL
+import Data.List.NonEmpty
+import Data.Maybe (isNothing)
+import Data.Ollama.Common.Utils as CU
+import Data.Text (Text)
+import Data.Text qualified as T
+import Data.Time (UTCTime)
+import GHC.Generics
+import GHC.Int (Int64)
+import Network.HTTP.Client
+
+-- | Enumerated roles that can participate in a chat.
+data Role = System | User | Assistant | Tool
+  deriving (Show, Eq)
+
+instance ToJSON Role where
+  toJSON System = String "system"
+  toJSON User = String "user"
+  toJSON Assistant = String "assistant"
+  toJSON Tool = String "tool"
+
+instance FromJSON Role where
+  parseJSON (String "system") = pure System
+  parseJSON (String "user") = pure User
+  parseJSON (String "assistant") = pure Assistant
+  parseJSON (String "tool") = pure Tool
+  parseJSON _ = fail "Invalid Role value"
+
+-- TODO : Add tool_calls parameter
+-- | Represents a message within a chat, including its role and content.
+data Message = Message
+  { role :: Role
+  -- ^ The role of the entity sending the message (e.g., 'User', 'Assistant').
+  , content :: Text
+  -- ^ The textual content of the message.
+  , images :: Maybe [Text]
+  -- ^ Optional list of base64 encoded images that accompany the message.
+  }
+  deriving (Show, Eq, Generic, ToJSON, FromJSON)
+
+-- TODO: Add Options parameter
+data ChatOps = ChatOps
+  { chatModelName :: Text
+ -- ^ The name of the chat model to be used.
+  , messages :: NonEmpty Message
+  -- ^ A non-empty list of messages forming the conversation context.
+  , tools :: Maybe Text
+  -- ^ Optional tools that may be used in the chat.
+  , format :: Maybe Text
+  -- ^ An optional format for the chat response.
+  , stream :: Maybe (ChatResponse -> IO (), IO ())
+  -- ^ Optional streaming functions where the first handles each chunk of the response, and the second flushes the stream.
+  , keepAlive :: Maybe Text
+  -- ^ Optional text to specify keep-alive behavior.  
+  }
+
+instance Show ChatOps where
+  show (ChatOps {chatModelName = m, messages = ms, tools = t, format = f, keepAlive = ka}) =
+    let messagesStr = show (toList ms)
+        toolsStr = show t
+        formatStr = show f
+        keepAliveStr = show ka
+     in T.unpack m
+          ++ "\nMessages:\n"
+          ++ messagesStr
+          ++ "\n"
+          ++ toolsStr
+          ++ "\n"
+          ++ formatStr
+          ++ "\n"
+          ++ keepAliveStr
+
+instance Eq ChatOps where
+  (==) a b =
+    chatModelName a == chatModelName b
+      && messages a == messages b
+      && tools a == tools b
+      && format a == format b
+      && keepAlive a == keepAlive b
+
+data ChatResponse = ChatResponse
+  { model :: Text
+ -- ^ The name of the model that generated this response.
+  , createdAt :: UTCTime
+  -- ^ The timestamp when the response was created.
+  , message :: Maybe Message
+  -- ^ The message content of the response, if any.
+  , done :: Bool
+  -- ^ Indicates whether the chat process has completed.
+  , totalDuration :: Maybe Int64
+  -- ^ Optional total duration in milliseconds for the chat process.
+  , loadDuration :: Maybe Int64
+  -- ^ Optional load duration in milliseconds for loading the model.
+  , promptEvalCount :: Maybe Int64
+  -- ^ Optional count of prompt evaluations during the chat process.
+  , promptEvalDuration :: Maybe Int64
+  -- ^ Optional duration in milliseconds for evaluating the prompt.
+  , evalCount :: Maybe Int64
+  -- ^ Optional count of evaluations during the chat process.
+  , evalDuration :: Maybe Int64
+  -- ^ Optional duration in milliseconds for evaluations during the chat process.  
+  }
+  deriving (Show, Eq)
+
+instance ToJSON ChatOps where
+  toJSON (ChatOps model messages tools format stream keepAlive) =
+    object
+      [ "model" .= model
+      , "messages" .= messages
+      , "tools" .= tools
+      , "format" .= format
+      , "stream" .= if isNothing stream then Just False else Just True
+      , "keep_alive" .= keepAlive
+      ]
+
+instance FromJSON ChatResponse where
+  parseJSON = withObject "ChatResponse" $ \v ->
+    ChatResponse
+      <$> v .: "model"
+      <*> v .: "created_at"
+      <*> v .: "message"
+      <*> v .: "done"
+      <*> v .:? "total_duration"
+      <*> v .:? "load_duration"
+      <*> v .:? "prompt_eval_count"
+      <*> v .:? "prompt_eval_duration"
+      <*> v .:? "eval_count"
+      <*> v .:? "eval_duration"
+
+-- | 
+-- A default configuration for initiating a chat with a model. 
+-- This can be used as a starting point and modified as needed.
+-- 
+-- Example:
+-- 
+-- > let ops = defaultChatOps { chatModelName = "customModel" }
+-- > chat ops
+defaultChatOps :: ChatOps
+defaultChatOps =
+  ChatOps
+    { chatModelName = "llama3.2"
+    , messages = Message User "What is 2+2?" Nothing :| []
+    , tools = Nothing
+    , format = Nothing
+    , stream = Nothing
+    , keepAlive = Nothing
+    }
+
+-- | 
+-- Initiates a chat session with the specified 'ChatOps' configuration and returns either
+-- a 'ChatResponse' or an error message.
+--
+-- This function sends a request to the Ollama chat API with the given options.
+-- 
+-- Example:
+--
+-- > let ops = defaultChatOps
+-- > result <- chat ops
+-- > case result of
+-- >   Left errorMsg -> putStrLn ("Error: " ++ errorMsg)
+-- >   Right response -> print response
+chat :: ChatOps -> IO (Either String ChatResponse)
+chat cOps = do
+  let url = CU.host defaultOllama
+  manager <- newManager defaultManagerSettings
+  initialRequest <- parseRequest $ T.unpack (url <> "/api/chat")
+  let reqBody = cOps
+      request =
+        initialRequest
+          { method = "POST"
+          , requestBody = RequestBodyLBS $ encode reqBody
+          }
+  withResponse request manager $ \response -> do
+    let streamResponse sendChunk flush = do
+          bs <- brRead $ responseBody response
+          if BS.null bs
+            then putStrLn "" >> pure (Left "")
+            else do
+              let eRes = eitherDecode (BSL.fromStrict bs) :: Either String ChatResponse
+              case eRes of
+                Left e -> pure (Left e)
+                Right r -> do
+                  _ <- sendChunk r
+                  _ <- flush
+                  if done r then pure (Left "") else streamResponse sendChunk flush
+    let genResponse op = do
+          bs <- brRead $ responseBody response
+          if BS.null bs
+            then do
+              let eRes = eitherDecode (BSL.fromStrict op) :: Either String ChatResponse
+              case eRes of
+                Left e -> pure (Left e)
+                Right r -> pure (Right r)
+            else genResponse (op <> bs)
+    case stream cOps of
+      Nothing -> genResponse ""
+      Just (sendChunk, flush) -> streamResponse sendChunk flush
diff --git a/src/Data/Ollama/Common/Types.hs b/src/Data/Ollama/Common/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Ollama/Common/Types.hs
@@ -0,0 +1,32 @@
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Data.Ollama.Common.Types where
+
+import Data.Aeson
+import Data.Text (Text)
+
+data ModelDetails = ModelDetails
+  { parentModel :: Maybe Text
+  , format :: Text
+  , familiy :: Text
+  , families :: [Text]
+  , parameterSize :: Text
+  , quantizationLevel :: Text
+  }
+  deriving (Eq, Show)
+
+instance FromJSON ModelDetails where
+  parseJSON = withObject "ModelDetails" $ \v ->
+    ModelDetails
+      <$> v .: "parent_model"
+      <*> v .: "format"
+      <*> v .: "family"
+      <*> v .:? "families" .!= []
+      <*> v .: "parameter_size"
+      <*> v .: "quantization_level"
+
+newtype OllamaClient = OllamaClient
+  { host :: Text
+  }
+  deriving (Eq, Show)
diff --git a/src/Data/Ollama/Common/Utils.hs b/src/Data/Ollama/Common/Utils.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Ollama/Common/Utils.hs
@@ -0,0 +1,8 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Data.Ollama.Common.Utils (defaultOllama, OllamaClient (..)) where
+
+import Data.Ollama.Common.Types
+
+defaultOllama :: OllamaClient
+defaultOllama = OllamaClient "http://127.0.0.1:11434"
diff --git a/src/Data/Ollama/Copy.hs b/src/Data/Ollama/Copy.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Ollama/Copy.hs
@@ -0,0 +1,54 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Data.Ollama.Copy
+  ( -- * Copy Model API
+    copyModel
+  ) where
+
+import Control.Monad (when)
+import Data.Aeson
+import Data.Ollama.Common.Utils qualified as CU
+import Data.Text (Text)
+import Data.Text qualified as T
+import GHC.Generics
+import Network.HTTP.Client
+import Network.HTTP.Types.Status (status404)
+
+-- TODO: Add Options parameter
+-- TODO: Add Context parameter
+data CopyModelOps = CopyModelOps
+  { source :: Text
+  , destination :: Text
+  }
+  deriving (Show, Eq, Generic, ToJSON)
+
+-- | Copy model from source to destination
+copyModel ::
+  -- | Source model
+  Text ->
+  -- | Destination model
+  Text ->
+  IO ()
+copyModel
+  source
+  destination =
+    do
+      let url = CU.host CU.defaultOllama
+      manager <- newManager defaultManagerSettings
+      initialRequest <- parseRequest $ T.unpack (url <> "/api/copy")
+      let reqBody =
+            CopyModelOps
+              { source = source
+              , destination = destination
+              }
+          request =
+            initialRequest
+              { method = "POST"
+              , requestBody = RequestBodyLBS $ encode reqBody
+              }
+      response <- httpLbs request manager
+      when
+        (responseStatus response == status404)
+        (putStrLn "Source Model does not exist")
diff --git a/src/Data/Ollama/Create.hs b/src/Data/Ollama/Create.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Ollama/Create.hs
@@ -0,0 +1,120 @@
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Data.Ollama.Create
+  ( -- * Create Model API
+    createModel
+  , createModelOps
+  ) where
+
+import Control.Monad (unless)
+import Data.Aeson
+import Data.ByteString.Lazy.Char8 qualified as BSL
+import Data.Ollama.Common.Utils as CU
+import Data.Text (Text)
+import Data.Text qualified as T
+import Data.Text.IO qualified as T
+import Network.HTTP.Client
+
+-- TODO: Add Options parameter
+-- TODO: Add Context parameter
+data CreateModelOps = CreateModelOps
+  { name :: Text
+  , modelFile :: Maybe Text
+  , stream :: Maybe Bool
+  , path :: Maybe FilePath
+  }
+  deriving (Show, Eq)
+
+-- TODO: Add Context Param
+newtype CreateModelResp = CreateModelResp {status :: Text}
+  deriving (Show, Eq)
+
+instance ToJSON CreateModelOps where
+  toJSON
+    ( CreateModelOps
+        name
+        modelFile
+        stream
+        path
+      ) =
+      object
+        [ "name" .= name
+        , "modelfile" .= modelFile
+        , "stream" .= stream
+        , "path" .= path
+        ]
+
+instance FromJSON CreateModelResp where
+  parseJSON = withObject "CreateModelResp" $ \v ->
+    CreateModelResp
+      <$> v .: "status"
+
+{- | Create a new model either from ModelFile or Path
+Please note, if you specify both ModelFile and Path, ModelFile will be used.
+-}
+createModelOps ::
+  -- | Model Name
+  Text ->
+  -- | Model File
+  Maybe Text ->
+  -- | Stream
+  Maybe Bool ->
+  -- | Path
+  Maybe FilePath ->
+  IO ()
+createModelOps
+  modelName
+  modelFile
+  stream
+  path =
+    do
+      let url = CU.host defaultOllama
+      manager <- newManager defaultManagerSettings
+      initialRequest <- parseRequest $ T.unpack (url <> "/api/create")
+      let reqBody =
+            CreateModelOps
+              { name = modelName
+              , modelFile = modelFile
+              , stream = stream
+              , path = path
+              }
+          request =
+            initialRequest
+              { method = "POST"
+              , requestBody = RequestBodyLBS $ encode reqBody
+              }
+      withResponse request manager $ \response -> do
+        let go = do
+              bs <- brRead $ responseBody response
+              let eRes =
+                    eitherDecode (BSL.fromStrict bs) ::
+                      Either String CreateModelResp
+              case eRes of
+                Left err -> do
+                  putStrLn $ "Error: " <> err
+                Right res -> do
+                  unless
+                    (status res /= "success")
+                    ( do
+                        T.putStr $ status res
+                        go
+                    )
+        go
+
+{- | Create a new model
+| Please note, if you specify both ModelFile and Path, ModelFile will be used.
+-}
+createModel ::
+  -- | Model Name
+  Text ->
+  -- | Model File
+  Maybe Text ->
+  -- | Path
+  Maybe FilePath ->
+  IO ()
+createModel modelName modelFile =
+  createModelOps
+    modelName
+    modelFile
+    Nothing
diff --git a/src/Data/Ollama/Delete.hs b/src/Data/Ollama/Delete.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Ollama/Delete.hs
@@ -0,0 +1,43 @@
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Data.Ollama.Delete
+  ( -- * Delete downloaded Models
+    deleteModel
+  ) where
+
+import Control.Monad (when)
+import Data.Aeson
+import Data.Ollama.Common.Utils qualified as CU
+import Data.Text (Text)
+import Data.Text qualified as T
+import Network.HTTP.Client
+import Network.HTTP.Types.Status (status404)
+
+-- TODO: Add Options parameter
+-- TODO: Add Context parameter
+newtype DeleteModelReq = DeleteModelReq {name :: Text}
+  deriving newtype (Show, Eq, ToJSON)
+
+-- | Delete a model
+deleteModel ::
+  -- | Model name
+  Text ->
+  IO ()
+deleteModel modelName =
+  do
+    let url = CU.host CU.defaultOllama
+    manager <- newManager defaultManagerSettings
+    initialRequest <- parseRequest $ T.unpack (url <> "/api/delete")
+    let reqBody =
+          DeleteModelReq {name = modelName}
+        request =
+          initialRequest
+            { method = "DELETE"
+            , requestBody = RequestBodyLBS $ encode reqBody
+            }
+    response <- httpLbs request manager
+    when
+      (responseStatus response == status404)
+      (putStrLn "Model does not exist")
diff --git a/src/Data/Ollama/Embeddings.hs b/src/Data/Ollama/Embeddings.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Ollama/Embeddings.hs
@@ -0,0 +1,87 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Data.Ollama.Embeddings
+  ( -- * Embedding API
+    embedding
+  , embeddingOps
+  ) where
+
+import Data.Aeson
+import Data.Ollama.Common.Utils as CU
+import Data.Text (Text)
+import Data.Text qualified as T
+import GHC.Generics
+import Network.HTTP.Client
+
+-- TODO: Add Options parameter
+data EmbeddingOps = EmbeddingOps
+  { model :: Text
+  , input :: Text
+  , truncate :: Maybe Bool
+  , keepAlive :: Maybe Text
+  }
+  deriving (Show, Eq)
+
+data EmbeddingResp = EmbeddingResp
+  { model :: Text
+  , embedding' :: [[Float]]
+  }
+  deriving (Show, Eq, Generic, FromJSON)
+
+instance ToJSON EmbeddingOps where
+  toJSON (EmbeddingOps model input truncate' keepAlive) =
+    object
+      [ "model" .= model
+      , "input" .= input
+      , "truncate" .= truncate'
+      , "keep_alive" .= keepAlive
+      ]
+
+-- TODO: Add Options parameter
+
+-- | Embedding API
+embeddingOps ::
+  -- | Model
+  Text ->
+  -- | Input
+  Text ->
+  -- | Truncate
+  Maybe Bool ->
+  -- | Keep Alive
+  Maybe Text ->
+  IO (Maybe EmbeddingResp)
+embeddingOps modelName input mTruncate mKeepAlive = do
+  let url = CU.host defaultOllama
+  manager <- newManager defaultManagerSettings
+  initialRequest <- parseRequest $ T.unpack (url <> "/api/embed")
+  let reqBody =
+        EmbeddingOps
+          { model = modelName
+          , input = input
+          , truncate = mTruncate
+          , keepAlive = mKeepAlive
+          }
+      request =
+        initialRequest
+          { method = "POST"
+          , requestBody = RequestBodyLBS $ encode reqBody
+          }
+  resp <- httpLbs request manager
+  let mRes = decode (responseBody resp) :: Maybe EmbeddingResp
+  case mRes of
+    Nothing -> pure Nothing
+    Just r -> pure $ Just r
+
+-- Higher level binding that only takes important params
+
+-- | Embedding API
+embedding ::
+  -- | Model
+  Text ->
+  -- | Input
+  Text ->
+  IO (Maybe EmbeddingResp)
+embedding modelName input =
+  embeddingOps modelName input Nothing Nothing
diff --git a/src/Data/Ollama/Generate.hs b/src/Data/Ollama/Generate.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Ollama/Generate.hs
@@ -0,0 +1,272 @@
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+
+module Data.Ollama.Generate
+  ( -- * Generate Texts
+    generate
+  , defaultGenerateOps
+  , GenerateOps (..)
+  , GenerateResponse (..)
+  ) where
+
+import Data.Aeson
+import Data.ByteString.Char8 qualified as BS
+import Data.ByteString.Lazy.Char8 qualified as BSL
+import Data.Maybe
+import Data.Ollama.Common.Utils as CU
+import Data.Text (Text)
+import Data.Text qualified as T
+import Data.Time (UTCTime)
+import GHC.Int (Int64)
+import Network.HTTP.Client
+
+-- TODO: Add Options parameter
+-- TODO: Add Context parameter
+
+{- | 
+  Input type for generate functions. This data type represents all possible configurations 
+  that you can pass to the Ollama generate API.
+  
+  Example:
+  
+  > let ops = GenerateOps 
+  >         { modelName = "llama3.2"
+  >         , prompt = "What is the meaning of life?"
+  >         , suffix = Nothing
+  >         , images = Nothing
+  >         , format = Just "text"
+  >         , system = Nothing
+  >         , template = Nothing
+  >         , stream = Nothing
+  >         , raw = Just False
+  >         , keepAlive = Just "yes"
+  >         }
+-}
+data GenerateOps = GenerateOps
+  { modelName :: Text
+  -- ^ The name of the model to be used for generation.
+  , prompt :: Text
+  -- ^ The prompt text that will be provided to the model for generating a response.
+  , suffix :: Maybe Text
+  -- ^ An optional suffix to append to the generated text.
+  , images :: Maybe [Text]
+  -- ^ Optional list of base64 encoded images to include with the request.
+  , format :: Maybe Text
+  -- ^ An optional format specifier for the response.
+  , system :: Maybe Text
+  -- ^ Optional system text that can be included in the generation context.
+  , template :: Maybe Text
+  -- ^ An optional template to format the response.
+  , stream :: Maybe (GenerateResponse -> IO (), IO ())
+  -- ^ An optional streaming function where the first function handles each chunk of response, and the second flushes the stream.
+  , raw :: Maybe Bool
+  -- ^ An optional flag to return the raw response.
+  , keepAlive :: Maybe Text
+  -- ^ Optional text to specify keep-alive behavior.
+  }
+
+instance Show GenerateOps where
+  show GenerateOps {..} =
+    "GenerateOps { "
+      <> "model : "
+      <> T.unpack modelName
+      <> ", prompt : "
+      <> T.unpack prompt
+      <> ", suffix : "
+      <> show suffix
+      <> ", images : "
+      <> show images
+      <> ", format : "
+      <> show format
+      <> ", system : "
+      <> show system
+      <> ", template : "
+      <> show template
+      <> ", stream : "
+      <> "Stream functions"
+      <> ", raw : "
+      <> show raw
+      <> ", keepAlive : "
+      <> show keepAlive
+
+instance Eq GenerateOps where
+    (==) a b = 
+        modelName a == modelName b &&
+        prompt a == prompt b &&
+        suffix a == suffix b &&
+        images a == images b &&
+        format a == format b &&
+        system a == system b &&
+        template a == template b &&
+        raw a == raw b &&
+        keepAlive a == keepAlive b
+
+-- TODO: Add Context Param
+
+-- | 
+-- Result type for generate function containing the model's response and meta-information.
+data GenerateResponse = GenerateResponse
+  { model :: Text
+  -- ^ The name of the model that generated the response.
+  , createdAt :: UTCTime
+  -- ^ The timestamp when the response was created.
+  , response_ :: Text
+  -- ^ The generated response from the model.
+  , done :: Bool
+  -- ^ A flag indicating whether the generation process is complete.
+  , totalDuration :: Maybe Int64
+  -- ^ Optional total duration in milliseconds for the generation process.
+  , loadDuration :: Maybe Int64
+  -- ^ Optional load duration in milliseconds for loading the model.
+  , promptEvalCount :: Maybe Int64
+  -- ^ Optional count of prompt evaluations during the generation process.
+  , promptEvalDuration :: Maybe Int64
+  -- ^ Optional duration in milliseconds for evaluating the prompt.
+  , evalCount :: Maybe Int64
+  -- ^ Optional count of evaluations during the generation process.
+  , evalDuration :: Maybe Int64
+  -- ^ Optional duration in milliseconds for evaluations during the generation process.
+  }
+  deriving (Show, Eq)
+
+instance ToJSON GenerateOps where
+  toJSON
+    ( GenerateOps
+        model
+        prompt
+        suffix
+        images
+        format
+        system
+        template
+        stream
+        raw
+        keepAlive
+      ) =
+      object
+        [ "model" .= model
+        , "prompt" .= prompt
+        , "suffix" .= suffix
+        , "images" .= images
+        , "format" .= format
+        , "system" .= system
+        , "template" .= template
+        , "stream" .= if isNothing stream then Just False else Just True
+        , "raw" .= raw
+        , "keep_alive" .= keepAlive
+        ]
+
+instance FromJSON GenerateResponse where
+  parseJSON = withObject "GenerateResponse" $ \v ->
+    GenerateResponse
+      <$> v .: "model"
+      <*> v .: "created_at"
+      <*> v .: "response"
+      <*> v .: "done"
+      <*> v .:? "total_duration"
+      <*> v .:? "load_duration"
+      <*> v .:? "prompt_eval_count"
+      <*> v .:? "prompt_eval_duration"
+      <*> v .:? "eval_count"
+      <*> v .:? "eval_duration"
+
+-- | 
+-- A function to create a default 'GenerateOps' type with preset values.
+-- 
+-- Example:
+--
+-- > let ops = defaultGenerateOps
+-- > generate ops
+-- 
+-- This will generate a response using the default configuration.
+defaultGenerateOps :: GenerateOps
+defaultGenerateOps =
+  GenerateOps
+    { modelName = "llama3.2"
+    , prompt = "what is 2+2"
+    , suffix = Nothing
+    , images = Nothing
+    , format = Nothing
+    , system = Nothing
+    , template = Nothing
+    , stream = Nothing
+    , raw = Nothing
+    , keepAlive = Nothing
+    }
+
+-- | 
+-- Generate function that returns either a 'GenerateResponse' type or an error message.
+-- It takes a 'GenerateOps' configuration and performs a request to the Ollama generate API.
+-- 
+-- Examples:
+--
+-- Basic usage without streaming:
+--
+-- > let ops = GenerateOps 
+-- >         { modelName = "llama3.2"
+-- >         , prompt = "Tell me a joke."
+-- >         , suffix = Nothing
+-- >         , images = Nothing
+-- >         , format = Nothing
+-- >         , system = Nothing
+-- >         , template = Nothing
+-- >         , stream = Nothing
+-- >         , raw = Nothing
+-- >         , keepAlive = Nothing
+-- >         }
+-- > result <- generate ops
+-- > case result of
+-- >   Left errorMsg -> putStrLn ("Error: " ++ errorMsg)
+-- >   Right response -> print response
+--
+-- Usage with streaming to print responses to the console:
+--
+-- > void $
+-- >   generate
+-- >     defaultGenerateOps
+-- >       { modelName = "llama3.2"
+-- >       , prompt = "what is functional programming?"
+-- >       , stream = Just (T.putStr . response_, pure ())
+-- >       }
+--
+-- In this example, the first function in the 'stream' tuple processes each chunk of response by printing it,
+-- and the second function is a simple no-op flush.generate :: GenerateOps -> IO (Either String GenerateResponse)
+generate :: GenerateOps -> IO (Either String GenerateResponse)
+generate genOps = do
+  let url = CU.host defaultOllama
+  manager <-
+    newManager -- Setting response timeout to 5 minutes, since llm takes time
+      defaultManagerSettings {managerResponseTimeout = responseTimeoutMicro (5 * 60 * 1000000)}
+  initialRequest <- parseRequest $ T.unpack (url <> "/api/generate")
+  let reqBody = genOps
+      request =
+        initialRequest
+          { method = "POST"
+          , requestBody = RequestBodyLBS $ encode reqBody
+          }
+  withResponse request manager $ \response -> do
+    let streamResponse sendChunk flush = do
+          bs <- brRead $ responseBody response
+          if BS.null bs
+            then putStrLn "" >> pure (Left "")
+            else do
+              let eRes = eitherDecode (BSL.fromStrict bs) :: Either String GenerateResponse
+              case eRes of
+                Left e -> pure (Left e)
+                Right r -> do
+                  _ <- sendChunk r
+                  _ <- flush
+                  if done r then pure (Left "") else streamResponse sendChunk flush
+    let genResponse op = do
+          bs <- brRead $ responseBody response
+          if bs == ""
+            then do
+              let eRes0 = eitherDecode (BSL.fromStrict op) :: Either String GenerateResponse
+              case eRes0 of
+                Left e -> pure (Left e)
+                Right r -> pure (Right r)
+            else genResponse (op <> bs)
+    case stream genOps of
+      Nothing -> genResponse ""
+      Just (sendChunk, flush) -> streamResponse sendChunk flush
diff --git a/src/Data/Ollama/List.hs b/src/Data/Ollama/List.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Ollama/List.hs
@@ -0,0 +1,59 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Data.Ollama.List
+  ( -- * List Models API
+    list
+  , Models (..)
+  , ModelInfo (..)
+  )
+where
+
+import Data.Aeson
+import Data.Ollama.Common.Types as CT
+import Data.Ollama.Common.Utils as CU
+import Data.Text (Text)
+import Data.Text qualified as T
+import Data.Time
+import GHC.Int (Int64)
+import Network.HTTP.Client
+import Network.HTTP.Types.Status (statusCode)
+
+newtype Models = Models [ModelInfo]
+  deriving (Eq, Show)
+
+data ModelInfo = ModelInfo
+  { name :: Text
+  , modifiedAt :: UTCTime
+  , size :: Int64
+  , digest :: Text
+  , details :: ModelDetails
+  }
+  deriving (Eq, Show)
+
+-- Instances
+instance FromJSON Models where
+  parseJSON = withObject "Models" $ \v -> Models <$> v .: "models"
+
+instance FromJSON ModelInfo where
+  parseJSON = withObject "ModelInfo" $ \v ->
+    ModelInfo
+      <$> v .: "name"
+      <*> v .: "modified_at"
+      <*> v .: "size"
+      <*> v .: "digest"
+      <*> v .: "details"
+
+-- | List all models from local
+list :: IO (Maybe Models)
+list = do
+  let url = CU.host defaultOllama
+  manager <- newManager defaultManagerSettings
+  request <- parseRequest $ T.unpack (url <> "/api/tags")
+  response <- httpLbs request manager
+  if statusCode (responseStatus response) /= 200
+    then pure Nothing
+    else do
+      let res = decode (responseBody response) :: Maybe Models
+      case res of
+        Nothing -> pure Nothing
+        Just l -> pure $ Just l
diff --git a/src/Data/Ollama/Ps.hs b/src/Data/Ollama/Ps.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Ollama/Ps.hs
@@ -0,0 +1,61 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Data.Ollama.Ps
+  ( ps
+  , RunningModels (..)
+  , RunningModel (..)
+  ) where
+
+import Data.Aeson
+import Data.Ollama.Common.Types as CT
+import Data.Ollama.Common.Utils as CU
+import Data.Text (Text)
+import Data.Text qualified as T
+import Data.Time
+import GHC.Int (Int64)
+import Network.HTTP.Client
+import Network.HTTP.Types.Status (statusCode)
+
+-- Types for Ps API
+newtype RunningModels = RunningModels [RunningModel]
+  deriving (Eq, Show)
+
+data RunningModel = RunningModel
+  { name_ :: Text
+  , modelName :: Text
+  , size_ :: Int64
+  , modelDigest :: Text
+  , modelDetails :: ModelDetails
+  , expiresAt :: UTCTime
+  , sizeVRam :: Int64
+  }
+  deriving (Eq, Show)
+
+instance FromJSON RunningModels where
+  parseJSON = withObject "Models" $ \v -> RunningModels <$> v .: "models"
+
+instance FromJSON RunningModel where
+  parseJSON = withObject "RunningModel" $ \v ->
+    RunningModel
+      <$> v .: "name"
+      <*> v .: "model"
+      <*> v .: "size"
+      <*> v .: "digest"
+      <*> v .: "details"
+      <*> v .: "expires_at"
+      <*> v .: "size_vram"
+
+-- | List running models
+ps :: IO (Maybe RunningModels)
+ps = do
+  let url = CU.host defaultOllama
+  manager <- newManager defaultManagerSettings
+  request <- parseRequest $ T.unpack (url <> "/api/ps")
+  response <- httpLbs request manager
+  if statusCode (responseStatus response) /= 200
+    then pure Nothing
+    else do
+      let res = decode (responseBody response) :: Maybe RunningModels
+      case res of
+        Nothing -> pure Nothing
+        Just l -> pure $ Just l
diff --git a/src/Data/Ollama/Pull.hs b/src/Data/Ollama/Pull.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Ollama/Pull.hs
@@ -0,0 +1,113 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Data.Ollama.Pull
+  ( -- * Downloaded Models
+    pull
+  , pullOps
+  ) where
+
+import Data.Aeson
+import Data.ByteString.Lazy.Char8 qualified as BSL
+import Data.Maybe (fromMaybe)
+import Data.Ollama.Common.Utils as CU
+import Data.Text (Text)
+import Data.Text qualified as T
+import GHC.Generics
+import GHC.Int (Int64)
+import Network.HTTP.Client
+
+-- TODO: Add Options parameter
+
+-- | Configuration options for pulling a model.
+data PullOps = PullOps
+  { name :: Text
+  -- ^ The name of the model to pull.
+  , insecure :: Maybe Bool
+  -- ^ Option to allow insecure connections.
+  -- If set to 'Just True', the pull operation will allow insecure connections.
+  , stream :: Maybe Bool
+  -- ^ Option to enable streaming of the download.
+  -- If set to 'Just True', the download will be streamed.
+  }
+  deriving (Show, Eq, Generic, ToJSON)
+
+-- | Response data from a pull operation.
+data PullResp = PullResp
+  { status :: Text
+  -- ^ The status of the pull operation, e.g., "success" or "failure".
+  , digest :: Maybe Text
+  -- ^ The digest of the model, if available.
+  , total :: Maybe Int64
+  -- ^ The total size of the model in bytes, if available.
+  , completed :: Maybe Int64
+  -- ^ The number of bytes completed, if available.
+  }
+  deriving (Show, Eq, Generic, FromJSON)
+
+{- |
+Pull a model with additional options for insecure connections and streaming.
+This function interacts directly with the Ollama API to download the specified model.
+
+Example:
+
+> pullOps "myModel" (Just True) (Just True)
+
+This will attempt to pull "myModel" with insecure connections allowed and enable streaming.
+-}
+pullOps ::
+  -- | Model Name
+  Text ->
+  -- | Insecure
+  Maybe Bool ->
+  -- | Stream
+  Maybe Bool ->
+  IO ()
+pullOps modelName mInsecure mStream = do
+  let url = CU.host defaultOllama
+  manager <- newManager defaultManagerSettings
+  initialRequest <- parseRequest $ T.unpack (url <> "/api/pull")
+  let reqBody =
+        PullOps
+          { name = modelName
+          , insecure = mInsecure
+          , stream = mStream
+          }
+      request =
+        initialRequest
+          { method = "POST"
+          , requestBody = RequestBodyLBS $ encode reqBody
+          }
+  withResponse request manager $ \response -> do
+    let go = do
+          bs <- brRead $ responseBody response
+          let eRes = decode (BSL.fromStrict bs) :: Maybe PullResp
+          case eRes of
+            Nothing -> putStrLn "Something went wrong"
+            Just res -> do
+              if status res /= "success"
+                then do
+                  let completed' = fromMaybe 0 (completed res)
+                  let total' = fromMaybe 0 (total res)
+                  putStrLn $ "Remaining bytes: " <> show (total' - completed')
+                  go
+                else do
+                  putStrLn "Completed"
+    go
+
+{- |
+Pull a model using default options. This simplifies the pull operation by
+not requiring additional options.
+
+Example:
+
+> pull "myModel"
+
+This will pull "myModel" using default settings (no insecure connections and no streaming).
+-}
+pull ::
+  -- | Model Name
+  Text ->
+  IO ()
+pull modelName = pullOps modelName Nothing Nothing
diff --git a/src/Data/Ollama/Push.hs b/src/Data/Ollama/Push.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Ollama/Push.hs
@@ -0,0 +1,84 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Data.Ollama.Push
+  ( -- * Push API
+    push
+  , pushOps
+  ) where
+
+import Data.Aeson
+import Data.ByteString.Lazy.Char8 qualified as BSL
+import Data.Maybe (fromMaybe)
+import Data.Ollama.Common.Utils as CU
+import Data.Text (Text)
+import Data.Text qualified as T
+import GHC.Generics
+import GHC.Int (Int64)
+import Network.HTTP.Client
+
+-- TODO: Add Options parameter
+data PushOps = PushOps
+  { name :: Text
+  , insecure :: Maybe Bool
+  , stream :: Maybe Bool
+  }
+  deriving (Show, Eq, Generic, ToJSON)
+
+data PushResp = PushResp
+  { status :: Text
+  , digest :: Maybe Text
+  , total :: Maybe Int64
+  }
+  deriving (Show, Eq, Generic, FromJSON)
+
+-- | Push a model with options
+pushOps ::
+  -- | Model name
+  Text ->
+  -- | Insecure
+  Maybe Bool ->
+  -- | Stream
+  Maybe Bool ->
+  IO ()
+pushOps modelName mInsecure mStream = do
+  let url = CU.host defaultOllama
+  manager <- newManager defaultManagerSettings
+  initialRequest <- parseRequest $ T.unpack (url <> "/api/push")
+  let reqBody =
+        PushOps
+          { name = modelName
+          , insecure = mInsecure
+          , stream = mStream
+          }
+      request =
+        initialRequest
+          { method = "POST"
+          , requestBody = RequestBodyLBS $ encode reqBody
+          }
+  withResponse request manager $ \response -> do
+    let go = do
+          bs <- brRead $ responseBody response
+          let eRes = decode (BSL.fromStrict bs) :: Maybe PushResp
+          case eRes of
+            Nothing -> putStrLn "Something went wrong"
+            Just res -> do
+              if status res /= "success"
+                then do
+                  let total' = fromMaybe 0 (total res)
+                  putStrLn $ "Remaining bytes: " <> show total'
+                  go
+                else do
+                  putStrLn "Completed"
+    go
+
+-- Higher level API for Pull
+-- This API is untested. Will test soon!
+
+-- | Push a model
+push ::
+  -- | Model name
+  Text ->
+  IO ()
+push modelName = pushOps modelName Nothing Nothing
diff --git a/src/Data/Ollama/Show.hs b/src/Data/Ollama/Show.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Ollama/Show.hs
@@ -0,0 +1,174 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Data.Ollama.Show
+  ( -- * Show Model Info API
+    showModel
+  , showModelOps
+  , ShowModelResponse (..)
+  ) where
+
+import Data.Aeson
+import Data.Ollama.Common.Utils qualified as CU
+import Data.Text (Text)
+import Data.Text qualified as T
+import GHC.Generics
+import GHC.Int (Int64)
+import Network.HTTP.Client
+
+-- TODO: Add Options parameter
+-- TODO: Add Context parameter
+
+{- |
+ #ShowModelOps#
+ Input parameters for show model information.
+-}
+data ShowModelOps = ShowModelOps
+  { name :: Text
+  , verbose :: Maybe Bool
+  }
+  deriving (Show, Eq, Generic, ToJSON)
+
+{- |
+ #ShowModelResponse#
+
+ Ouput structure for show model information.
+-}
+data ShowModelResponse = ShowModelResponse
+  { modelFile :: Text
+  , parameters :: Text
+  , template :: Text
+  , details :: ModelDetails
+  , modelInfo :: ModelInfo
+  }
+  deriving (Show, Eq)
+
+data ModelDetails = ModelDetails
+  { parentModel :: Text
+  , format :: Text
+  , familiy :: Text
+  , families :: [Text]
+  , parameterSize :: Text
+  , quantizationLevel :: Text
+  }
+  deriving (Show, Eq)
+
+data ModelInfo = ModelInfo
+  { generalArchitecture :: Maybe Text
+  , generalFileType :: Maybe Int
+  , generalParameterCount :: Maybe Int64
+  , generalQuantizationVersion :: Maybe Int
+  , llamaAttentionHeadCount :: Maybe Int
+  , llamaAttentionHeadCountKV :: Maybe Int
+  , llamaAttentionLayerNormRMSEpsilon :: Maybe Float
+  , llamaBlockCount :: Maybe Int
+  , llamaContextLength :: Maybe Int
+  , llamaEmbeddingLength :: Maybe Int
+  , llamaFeedForwardLength :: Maybe Int
+  , llamaRopeDimensionCount :: Maybe Int
+  , llamaRopeFreqBase :: Maybe Int64
+  , llamaVocabSize :: Maybe Int64
+  , tokenizerGgmlBosToken_id :: Maybe Int
+  , tokenizerGgmlEosToken_id :: Maybe Int
+  , tokenizerGgmlMerges :: Maybe [Text]
+  , tokenizerGgmlMode :: Maybe Text
+  , tokenizerGgmlPre :: Maybe Text
+  , tokenizerGgmlTokenType :: Maybe [Text]
+  , tokenizerGgmlTokens :: Maybe [Text]
+  }
+  deriving (Show, Eq)
+
+-- FromJSON instances
+
+-- | The instance for show model response
+instance FromJSON ShowModelResponse where
+  parseJSON = withObject "ShowModelResponse" $ \v ->
+    ShowModelResponse
+      <$> v .: "modelfile"
+      <*> v .: "parameters"
+      <*> v .: "template"
+      <*> v .: "details"
+      <*> v .: "model_info"
+
+instance FromJSON ModelDetails where
+  parseJSON = withObject "ModelDetails" $ \v ->
+    ModelDetails
+      <$> v .: "parent_model"
+      <*> v .: "format"
+      <*> v .: "family"
+      <*> v .: "families"
+      <*> v .: "parameter_size"
+      <*> v .: "quantization_level"
+
+instance FromJSON ModelInfo where
+  parseJSON = withObject "ModelInfo" $ \v ->
+    ModelInfo
+      <$> v .:? "general.architecture"
+      <*> v .:? "general.file_type"
+      <*> v .:? "general.parameter_count"
+      <*> v .:? "general.quantization_version"
+      <*> v .:? "llama.attention.head_count"
+      <*> v .:? "llama.attention.head_count_kv"
+      <*> v .:? "llama.attention.layer_norm_rms_epsilon"
+      <*> v .:? "llama.block_count"
+      <*> v .:? "llama.context_length"
+      <*> v .:? "llama.embedding_length"
+      <*> v .:? "llama.feed_forward_length"
+      <*> v .:? "llama.rope.dimension_count"
+      <*> v .:? "llama.rope.freq_base"
+      <*> v .:? "llama.vocab_size"
+      <*> v .:? "tokenizer.ggml.bos_token_id"
+      <*> v .:? "tokenizer.ggml.eos_token_id"
+      <*> v .:? "tokenizer.ggml.merges"
+      <*> v .:? "tokenizer.ggml.model"
+      <*> v .:? "tokenizer.ggml.pre"
+      <*> v .:? "tokenizer.ggml.token_type"
+      <*> v .:? "tokenizer.ggml.tokens"
+
+{- | Show given model's information with options.
+
+@since 1.0.0.0
+-}
+showModelOps ::
+  -- | model name
+  Text ->
+  -- | verbose
+  Maybe Bool ->
+  IO (Maybe ShowModelResponse)
+showModelOps
+  modelName
+  verbose =
+    do
+      let url = CU.host CU.defaultOllama
+      manager <- newManager defaultManagerSettings
+      initialRequest <- parseRequest $ T.unpack (url <> "/api/show")
+      let reqBody =
+            ShowModelOps
+              { name = modelName
+              , verbose = verbose
+              }
+          request =
+            initialRequest
+              { method = "POST"
+              , requestBody = RequestBodyLBS $ encode reqBody
+              }
+      response <- httpLbs request manager
+      let eRes =
+            eitherDecode (responseBody response) ::
+              Either String ShowModelResponse
+      case eRes of
+        Left _ -> pure Nothing
+        Right r -> pure $ Just r
+
+{- | Show given model's information.
+
+Higher level API for show.
+@since 1.0.0.0
+-}
+showModel ::
+  -- | model name
+  Text ->
+  IO (Maybe ShowModelResponse)
+showModel modelName =
+  showModelOps modelName Nothing
diff --git a/src/Lib.hs b/src/Lib.hs
new file mode 100644
--- /dev/null
+++ b/src/Lib.hs
@@ -0,0 +1,138 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+
+module Lib where
+
+import Control.Monad (void)
+import Data.List.NonEmpty (NonEmpty((:|)))
+import Data.Maybe (fromMaybe)
+import Data.Ollama.Chat qualified as Chat
+import Data.Text.IO qualified as T
+import Ollama (GenerateOps(..), Role(..), chat, defaultChatOps, defaultGenerateOps, generate)
+import Ollama qualified
+
+main :: IO ()
+main = do
+  -- Example 1: Streamed Text Generation
+  -- This example demonstrates how to generate text using a model and stream the output directly to the console.
+  -- The `stream` option enables processing of each chunk of the response as it arrives.
+  void $
+    generate
+      defaultGenerateOps
+        { modelName = "llama3.2"
+        , prompt = "what is functional programming?"
+        , stream = Just (T.putStr . Ollama.response_, pure ())
+        }
+
+  -- Example 2: Non-streamed Text Generation
+  -- This example shows how to generate text and handle the complete response.
+  -- The result is either an error message or the generated text.
+  eRes <-
+    generate
+      defaultGenerateOps
+        { modelName = "llama3.2"
+        , prompt = "What is 2+2?"
+        }
+  case eRes of
+    Left e -> putStrLn e
+    Right Ollama.GenerateResponse {..} -> T.putStrLn response_
+
+  -- Example 3: Chat with Streaming
+  -- This example demonstrates setting up a chat session with streaming enabled.
+  -- As messages are received, they are printed to the console.
+  let msg = Ollama.Message User "What is functional programming?" Nothing
+      defaultMsg = Ollama.Message User "" Nothing
+  void $
+    chat
+      defaultChatOps
+        { Chat.chatModelName = "llama3.2"
+        , Chat.messages = msg :| []
+        , Chat.stream =
+            Just (T.putStr . Chat.content . fromMaybe defaultMsg . Chat.message, pure ())
+        }
+
+  -- Example 4: Non-streamed Chat
+  -- Here, we handle a complete chat response, checking for potential errors.
+  eRes1 <-
+    chat
+      defaultChatOps
+        { Chat.chatModelName = "llama3.2"
+        , Chat.messages = msg :| []
+        }
+  case eRes1 of
+    Left e -> putStrLn e
+    Right r -> do
+      let mMessage = Ollama.message r
+      case mMessage of
+        Nothing -> putStrLn "Something went wrong"
+        Just res -> T.putStrLn $ Ollama.content res
+
+  -- Example 5: Check Model Status (ps)
+  -- This example checks the status of models using the `ps` function.
+  -- It outputs the status or details of the available models.
+  res <- Ollama.ps
+  print res
+
+  -- Example 6: Simple Embedding
+  -- This demonstrates how to request embeddings for a given text using a specific model.
+  void $ Ollama.embedding "llama3.1" "What is 5+2?"
+
+  -- Example 7: Embedding with Options
+  -- This example uses the `embeddingOps` function, allowing for additional configuration like options and streaming.
+  void $ Ollama.embeddingOps "llama3.1" "What is 5+2?" Nothing Nothing
+
+{-
+Scotty example:
+{-# LANGUAGE OverloadedStrings #-}
+
+module Main where
+
+import Web.Scotty
+import Control.Monad.IO.Class (liftIO)
+import Data.Aeson (FromJSON, ToJSON)
+import Data.Text (Text)
+import Data.Text qualified as T
+import Database.SQLite.Simple
+import Ollama (GenerateOps(..), defaultGenerateOps, generate)
+import Data.Maybe (fromRight)
+
+data PromptInput = PromptInput
+  { conversation_id :: Int
+  , prompt :: Text
+  } deriving (Show, Generic)
+
+instance FromJSON PromptInput
+instance ToJSON PromptInput
+
+main :: IO ()
+main = do
+  conn <- open "chat.db"
+  execute_ conn "CREATE TABLE IF NOT EXISTS conversation (convo_id INTEGER PRIMARY KEY, convo_title TEXT)"
+  execute_ conn "CREATE TABLE IF NOT EXISTS chats (chat_id INTEGER PRIMARY KEY, convo_id INTEGER, role TEXT, message TEXT, FOREIGN KEY(convo_id) REFERENCES conversation(convo_id))"
+  
+  scotty 3000 $ do
+    post "/chat" $ do
+      p <- jsonData :: ActionM PromptInput
+      let cId = conversation_id p
+      let trimmedP = T.dropEnd 3 $ T.drop 3 $ prompt p
+      newConvoId <- case cId of
+        -1 -> do
+          liftIO $ execute conn "INSERT INTO conversation (convo_title) VALUES (?)" (Only ("latest title" :: String))
+          [Only convoId] <- liftIO $ query_ conn "SELECT last_insert_rowid()" :: ActionM [Only Int]
+          pure convoId
+        _ -> pure cId
+
+      liftIO $ execute conn "INSERT INTO chats (convo_id, role, message) VALUES (?, 'user', ?)" (newConvoId, trimmedP)
+      
+      stream $ \sendChunk flush -> do
+        eRes <- generate defaultGenerateOps
+                { modelName = "llama3.2"
+                , prompt = prompt p
+                , stream = Just (sendChunk . T.pack, flush)
+                }
+        case eRes of
+            Left e -> return ()
+            Right r -> do
+                let res = response_ r
+                liftIO $ execute conn "INSERT INTO chats (convo_id, role, message) VALUES (?, 'ai', ?)" (newConvoId, res)
+-}
diff --git a/src/Ollama.hs b/src/Ollama.hs
new file mode 100644
--- /dev/null
+++ b/src/Ollama.hs
@@ -0,0 +1,85 @@
+{- |
+  #Ollama-Haskell#
+  This library lets you run LlMs from within Haskell projects. Inspired by `ollama-python`.
+-}
+module Ollama
+  ( -- * Main APIs
+
+    -- ** Generate Texts
+    generate
+  , defaultGenerateOps
+  , GenerateOps (..)
+  , GenerateResponse (..)
+
+    -- ** Chat with LLMs
+  , chat
+  , Role (..)
+  , defaultChatOps
+  , ChatResponse (..)
+  , ChatOps (..)
+
+    -- ** Embeddings
+  , embedding
+  , embeddingOps
+
+    -- * Ollama operations
+
+    -- ** Copy Models
+  , copyModel
+
+    -- ** Create Models
+  , createModel
+  , createModelOps
+
+    -- ** Delete Models
+  , deleteModel
+
+    -- ** List Models
+  , list
+
+    -- ** List currently running models
+  , ps
+
+    -- ** Push and Pull
+  , push
+  , pushOps
+  , pull
+  , pullOps
+
+    -- ** Show Model Info
+  , showModel
+  , showModelOps
+
+    -- * Types
+  , ShowModelResponse (..)
+  , Models (..)
+  , ModelInfo (..)
+  , RunningModels (..)
+  , RunningModel (..)
+  , Message (..)
+  )
+where
+
+import Data.Ollama.Chat
+  ( ChatOps (..)
+  , ChatResponse (..)
+  , Message (..)
+  , Role (..)
+  , chat
+  , defaultChatOps
+  )
+import Data.Ollama.Copy (copyModel)
+import Data.Ollama.Create (createModel, createModelOps)
+import Data.Ollama.Delete (deleteModel)
+import Data.Ollama.Embeddings (embedding, embeddingOps)
+import Data.Ollama.Generate
+  ( GenerateOps (..)
+  , GenerateResponse (..)
+  , defaultGenerateOps
+  , generate
+  )
+import Data.Ollama.List (ModelInfo (..), Models (..), list)
+import Data.Ollama.Ps (RunningModel (..), RunningModels (..), ps)
+import Data.Ollama.Pull (pull, pullOps)
+import Data.Ollama.Push (push, pushOps)
+import Data.Ollama.Show (ShowModelResponse (..), showModel, showModelOps)
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,75 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Main where
+
+import Ollama qualified
+import Ollama (defaultGenerateOps, GenerateOps(..), defaultChatOps, Role(..))
+import Data.Ollama.Chat qualified as Chat
+import Data.Text.IO qualified as T
+import Test.Tasty
+import Test.Tasty.HUnit
+import System.IO.Silently (capture)
+import Data.Either
+import Data.List.NonEmpty hiding (length)
+import Data.Maybe
+
+tests :: TestTree
+tests = testGroup "Tests" [
+       generateTest
+      , chatTest
+      , psTest
+      , showTest
+   ]
+
+generateTest :: TestTree
+generateTest = testGroup "Generate tests" [
+       testCase "generate stream" $ do
+          output <- capture $ Ollama.generate defaultGenerateOps { modelName = "smollm:360m"
+               , prompt = "what is 4 + 2?"
+               , stream = Just (T.putStr . Ollama.response_, pure ()) }
+          assertBool "Checking if generate function is printing anything" (length output > 0)
+    , testCase "Generate non-stream" $ do
+          eRes <- Ollama.generate defaultGenerateOps { modelName = "smollm:360m"
+               , prompt = "what is 4 + 2?" }
+          assertBool "Checking if generate function returns a valid value" (isRight eRes)
+    , testCase "Generate with invalid model" $ do
+          eRes <- Ollama.generate defaultGenerateOps { modelName = "invalid-model" }
+          assertBool "Expecting generation to fail with invalid model" (isLeft eRes)
+   ]
+
+chatTest :: TestTree
+chatTest = testGroup "Chat tests" [
+        testCase "chat stream" $ do
+          let msg = Ollama.Message User "What is 29 + 3?" Nothing
+              defaultMsg = Ollama.Message User "" Nothing
+          output <- capture $ Ollama.chat defaultChatOps { 
+                 Chat.chatModelName = "smollm:360m"
+               , Chat.messages = msg :| []
+               , Chat.stream = Just (T.putStr . Chat.content . fromMaybe defaultMsg . Chat.message, pure ())
+               }
+          assertBool "Checking if chat function is printing anything" (length output > 0)
+
+    , testCase "Chat non-stream" $ do
+          let msg = Ollama.Message User "What is 29 + 3?" Nothing
+          eRes <- Ollama.chat defaultChatOps { 
+                 Chat.chatModelName = "smollm:360m"
+               , Chat.messages = msg :| []
+               }
+          assertBool "Checking if chat function returns a valid value" (isRight eRes)
+   ]
+
+psTest :: TestTree
+psTest = testGroup "PS test" [
+       testCase "check ps" $ do
+           mRes <- Ollama.ps
+           assertBool "Check if ps returns anything" (isJust mRes)
+   ]
+
+showTest :: TestTree
+showTest = testGroup "Show test" [
+       testCase "check show" $ do
+           mRes <- Ollama.showModel "smollm:360m"
+           assertBool "Check if model exists or not" (isJust mRes)
+   ]
+
+main :: IO ()
+main = defaultMain tests
