diff --git a/Clckwrks/IrcBot.hs b/Clckwrks/IrcBot.hs
new file mode 100644
--- /dev/null
+++ b/Clckwrks/IrcBot.hs
@@ -0,0 +1,11 @@
+module Clckwrks.IrcBot
+   ( module Clckwrks.IrcBot.URL
+   , module Clckwrks.IrcBot.Monad
+   , module Clckwrks.IrcBot.Route
+   , module Clckwrks.IrcBot.Types
+   ) where
+
+import Clckwrks.IrcBot.URL
+import Clckwrks.IrcBot.Monad
+import Clckwrks.IrcBot.Route
+import Clckwrks.IrcBot.Types
diff --git a/Clckwrks/IrcBot/Acid.hs b/Clckwrks/IrcBot/Acid.hs
new file mode 100644
--- /dev/null
+++ b/Clckwrks/IrcBot/Acid.hs
@@ -0,0 +1,38 @@
+{-# LANGUAGE DeriveDataTypeable, RecordWildCards, TemplateHaskell, TypeFamilies #-}
+module Clckwrks.IrcBot.Acid where
+
+import Clckwrks.IrcBot.Types  (IrcConfig(..))
+import Control.Applicative    ((<$>))
+import Control.Monad.Reader   (ask)
+import Control.Monad.State    (modify)
+import Data.Acid              (Query, Update, makeAcidic)
+import Data.Data              (Data, Typeable)
+import Data.IxSet             (IxSet, (@=), getOne, empty, toList, updateIx)
+import Data.SafeCopy          (base, deriveSafeCopy)
+
+
+data IrcBotState = IrcBotState
+    { ircConfig :: IrcConfig
+    }
+    deriving (Eq, Ord, Read, Show, Data, Typeable)
+$(deriveSafeCopy 0 'base ''IrcBotState)
+
+initialIrcBotState :: IrcConfig -> IrcBotState
+initialIrcBotState initConfig
+    = IrcBotState
+      { ircConfig = initConfig
+      }
+
+getIrcConfig :: Query IrcBotState IrcConfig
+getIrcConfig = ircConfig <$> ask
+
+setIrcConfig :: IrcConfig -> Update IrcBotState ()
+setIrcConfig newConfig =
+    modify $ \s -> s { ircConfig = newConfig }
+
+
+$(makeAcidic ''IrcBotState
+   [ 'getIrcConfig
+   , 'setIrcConfig
+   ]
+ )
diff --git a/Clckwrks/IrcBot/Monad.hs b/Clckwrks/IrcBot/Monad.hs
new file mode 100644
--- /dev/null
+++ b/Clckwrks/IrcBot/Monad.hs
@@ -0,0 +1,118 @@
+{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, TypeSynonymInstances, OverloadedStrings, RecordWildCards, RankNTypes, FlexibleContexts, TypeFamilies #-}
+module Clckwrks.IrcBot.Monad where
+
+import Clckwrks                     (Clck(..), ClckT(..), ClckFormT, ClckState(..), ClckURL(..), mapClckT)
+import Clckwrks.Acid
+import Clckwrks.IrcBot.Acid
+import Clckwrks.IrcBot.PreProcess   (ircBotCmd)
+import Clckwrks.IrcBot.Types
+import Clckwrks.IrcBot.URL
+import Control.Applicative           ((<$>))
+import Control.Exception             (bracket, finally)
+import Control.Monad.Reader          (ReaderT(..), MonadReader(..))
+import Data.Acid                     (AcidState, query)
+import Data.Acid.Local               (createCheckpointAndClose, openLocalStateFrom)
+import qualified Data.Map            as Map
+import Data.Maybe                    (fromMaybe)
+import Data.Set                      (Set, insert)
+import qualified Data.Text           as T
+import Happstack.Server              (ServerPartT, Input)
+import Happstack.Server.Internal.Monads (FilterFun)
+import HSP                           (Attr((:=)), Attribute(MkAttr), EmbedAsChild(..), EmbedAsAttr(..), IsName(toName), XMLGenT(..), pAttrVal, XML)
+import Network                       (PortID(PortNumber))
+import Network.IRC.Bot.BotMonad      (BotMonad(..))
+import Network.IRC.Bot.Core          (BotConf(..), User(..), nullBotConf, simpleBot)
+import Network.IRC.Bot.Log           (LogLevel(..), nullLogger, stdoutLogger)
+import Network.IRC.Bot.Part.Dice     (dicePart)
+import Network.IRC.Bot.Part.Hello    (helloPart)
+import Network.IRC.Bot.Part.Ping     (pingPart)
+import Network.IRC.Bot.Part.NickUser (nickUserPart)
+import Network.IRC.Bot.Part.Channels (initChannelsPart)
+import Network.IRC.Bot.PosixLogger   (posixLogger)
+import System.Directory              (createDirectoryIfMissing)
+import System.FilePath               ((</>))
+import Text.Reform                   (CommonFormError, FormError(..))
+import Web.Routes                    (showURL)
+
+data IrcBotConfig = IrcBotConfig
+    { ircBotLogDirectory :: FilePath -- ^ directory in which to store irc logs
+    , ircBotState        :: AcidState IrcBotState
+    , ircBotClckURL      :: ClckURL -> [(T.Text, Maybe T.Text)] -> T.Text
+    , ircReconnect       :: IO ()
+    }
+
+type IrcBotT m = ClckT IrcBotURL (ReaderT IrcBotConfig m)
+type IrcBotM   = ClckT IrcBotURL (ReaderT IrcBotConfig (ServerPartT IO))
+
+data IrcFormError
+    = IrcCFE (CommonFormError [Input])
+    | CouldNotParsePort String
+      deriving (Show)
+
+instance FormError IrcFormError where
+    type ErrorInputType IrcFormError = [Input]
+    commonFormError = IrcCFE
+
+type IrcBotForm = ClckFormT IrcFormError IrcBotM
+
+instance (Functor m, Monad m) => EmbedAsChild (IrcBotT m) IrcFormError where
+    asChild e = asChild (show e)
+
+instance (IsName n) => EmbedAsAttr IrcBotM (Attr n IrcBotURL) where
+        asAttr (n := u) =
+            do url <- showURL u
+               asAttr $ MkAttr (toName n, pAttrVal (T.unpack url))
+
+instance (IsName n) => EmbedAsAttr IrcBotM (Attr n ClckURL) where
+        asAttr (n := url) =
+            do showFn <- ircBotClckURL <$> ask
+               asAttr $ MkAttr (toName n, pAttrVal (T.unpack $ showFn url []))
+
+runIrcBotT :: IrcBotConfig -> IrcBotT m a -> ClckT IrcBotURL m a
+runIrcBotT mc m = mapClckT f m
+    where
+      f r = runReaderT r mc
+
+instance (Monad m) => MonadReader IrcBotConfig (IrcBotT m) where
+    ask = ClckT $ ask
+    local f (ClckT m) = ClckT $ local f m
+
+instance (Functor m, Monad m) => GetAcidState (IrcBotT m) IrcBotState where
+    getAcidState =
+        ircBotState <$> ask
+{-
+withIrcBotConfig :: Maybe FilePath         -- ^ base path to state dir
+                 -> IrcConfig              -- ^ initial 'IrcConfig' to use when creating database for the first time
+                 -> (forall headers body.
+                     ( EmbedAsChild (Clck ClckURL) headers
+                     , EmbedAsChild (Clck ClckURL) body
+                     ) =>
+                       String
+                     -> headers
+                     -> body
+                     -> XMLGenT (Clck IrcBotURL) XML)
+                 -> FilePath               -- ^ directory where irc log files are storted
+                 -> (IrcBotConfig -> IO a) -- ^ function that uses the 'IrcBotConfig'
+                 -> IO a
+withIrcBotConfig mBasePath initIrcConfig pageTemplate' ircBotLogDir f =
+    do let basePath = fromMaybe "_state" mBasePath
+       bracket (openLocalStateFrom (basePath </> "ircBot") (initialIrcBotState initIrcConfig)) (createCheckpointAndClose) $ \ircBot ->
+           do ic@IrcConfig{..} <- query ircBot GetIrcConfig
+              let botConf = nullBotConf { channelLogger = Just $ posixLogger (Just ircBotLogDir) "#happs"
+                                        , host          = ircHost
+                                        , port          = PortNumber $ fromIntegral ircPort
+                                        , nick          = ircNick
+                                        , commandPrefix = ircCommandPrefix
+                                        , user          = ircUser
+                                        , channels      = ircChannels
+                                        , limits        = Just (5, 2000000)
+                                        }
+              ircParts <- initParts (channels botConf)
+              (tids, reconnect) <- simpleBot botConf ircParts
+              (f (IrcBotConfig { ircBotLogDirectory = ircBotLogDir
+                               , ircBotState        = ircBot
+--                               , ircBotClckURL      = undefined
+--                               , ircBotPageTemplate = pageTemplate'
+                               , ircReconnect       = reconnect
+                               })) `finally` (mapM_ killThread tids)
+-}
diff --git a/Clckwrks/IrcBot/Page/IrcLog.hs b/Clckwrks/IrcBot/Page/IrcLog.hs
new file mode 100644
--- /dev/null
+++ b/Clckwrks/IrcBot/Page/IrcLog.hs
@@ -0,0 +1,31 @@
+{-# OPTIONS_GHC -F -pgmFtrhsx #-}
+module Clckwrks.IrcBot.Page.IrcLog where
+
+import Control.Applicative   ((<$>))
+import Control.Monad.Reader  (ask)
+import Clckwrks
+import Clckwrks.IrcBot.Monad
+import Clckwrks.IrcBot.Page.Template (template)
+import Data.List (sort)
+import Data.Text (pack)
+import Data.Text.IO as T (readFile)
+import Happstack.Server.FileServe.BuildingBlocks (isSafePath)
+import System.Directory
+import System.FilePath
+import HSP
+import Happstack.Server
+import Happstack.Server.HSP.HTML
+
+ircLog :: FilePath -> IrcBotM Response
+ircLog logFile =
+    if isSafePath (splitDirectories logFile)
+    then case takeExtension logFile of
+           ".txt" ->
+               do logDir <- ircBotLogDirectory <$> ask
+                  c      <- liftIO $ T.readFile (logDir </> logFile)
+                  template (pack logFile) () <pre><% c %></pre>
+           ".html" ->
+               do logDir <- ircBotLogDirectory <$> ask
+                  serveFile (asContentType "text/html; charset=UTF-8") (logDir </> logFile)
+           _ -> notFound $ toResponse $ "not sure what to do with " ++ logFile
+    else do unauthorized $ toResponse $ "Access Denied to file " ++ logFile
diff --git a/Clckwrks/IrcBot/Page/IrcLogs.hs b/Clckwrks/IrcBot/Page/IrcLogs.hs
new file mode 100644
--- /dev/null
+++ b/Clckwrks/IrcBot/Page/IrcLogs.hs
@@ -0,0 +1,26 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# OPTIONS_GHC -F -pgmFtrhsx #-}
+module Clckwrks.IrcBot.Page.IrcLogs where
+
+import Control.Applicative   ((<$>))
+import Control.Monad.Reader  (ask)
+import Clckwrks
+import Clckwrks.IrcBot.Monad
+import Clckwrks.IrcBot.Page.Template (template)
+import Clckwrks.IrcBot.URL
+import Data.List (sort)
+import System.Directory
+import System.FilePath
+import HSP
+import Happstack.Server.HSP.HTML
+
+ircLogs :: IrcBotM Response
+ircLogs =
+    do logDir   <- ircBotLogDirectory <$> ask
+       logFiles <- liftIO $ (reverse . sort . filter ((\ext -> (ext == ".html") || (ext == ".txt")) . takeExtension)) <$> getDirectoryContents logDir
+       urls     <- mapM (showURL . IrcLog) logFiles
+       let logs = zip urls logFiles
+       template "irc logs" ()
+         <ul>
+           <% mapM (\(url, logFile) -> <li><a href=url><% logFile %></a></li>) logs %>
+         </ul>
diff --git a/Clckwrks/IrcBot/Page/Reconnect.hs b/Clckwrks/IrcBot/Page/Reconnect.hs
new file mode 100644
--- /dev/null
+++ b/Clckwrks/IrcBot/Page/Reconnect.hs
@@ -0,0 +1,38 @@
+{-# OPTIONS_GHC -F -pgmFtrhsx #-}
+module Clckwrks.IrcBot.Page.Reconnect where
+
+import Control.Applicative      ((<$>), (<*))
+import Control.Monad.Reader     (ask)
+import Control.Monad.Trans      (liftIO)
+import Clckwrks                 (update, seeOtherURL)
+import Clckwrks.Admin.Template  (template)
+import Clckwrks.IrcBot.Monad    (IrcBotM(..), IrcBotForm, IrcBotConfig(..))
+import Clckwrks.IrcBot.URL      (IrcBotURL)
+import           Data.Map       (Map)
+import qualified Data.Map       as Map
+import Happstack.Server         (Response, ok, setResponseCode, toResponse)
+import HSP
+import Text.Reform              ((++>))
+import Text.Reform.Happstack    (reform)
+import Text.Reform.HSP.String   (inputSubmit, form)
+import Web.Routes               (showURL)
+
+ircReconnectPage :: IrcBotURL -> IrcBotM Response
+ircReconnectPage here =
+    do action <- showURL here
+       template "Force Reconnect" () $
+                <%>
+                 <% reform (form action) "ir" forceReconnect Nothing forceReconnectForm %>
+                </%>
+    where
+      forceReconnect :: Maybe String -> IrcBotM Response
+      forceReconnect _ =
+          do fr <- ircReconnect <$> ask
+             liftIO $ putStrLn "attempting reconnect"
+             liftIO $ fr
+             template "forced reconnect" () $
+                      <p>forced bot to reconnect</p>
+
+forceReconnectForm :: IrcBotForm (Maybe String)
+forceReconnectForm =
+    inputSubmit "Force Reconnect"
diff --git a/Clckwrks/IrcBot/Page/Settings.hs b/Clckwrks/IrcBot/Page/Settings.hs
new file mode 100644
--- /dev/null
+++ b/Clckwrks/IrcBot/Page/Settings.hs
@@ -0,0 +1,88 @@
+{-# LANGUAGE RecordWildCards #-}
+{-# OPTIONS_GHC -F -pgmFtrhsx #-}
+module Clckwrks.IrcBot.Page.Settings where
+
+import Control.Applicative      ((<$>), (<*>), (<*))
+import Control.Monad.Reader     (ask)
+import Control.Monad.Trans      (liftIO)
+import Clckwrks                 (ClckFormT, query, update, seeOtherURL)
+import Clckwrks.Admin.Template  (template)
+import Clckwrks.IrcBot.Acid     (GetIrcConfig(..), SetIrcConfig(..))
+import Clckwrks.IrcBot.Monad    (IrcBotM(..), IrcBotConfig(..), IrcBotForm, IrcFormError(..))
+import Clckwrks.IrcBot.Types    (IrcConfig(..))
+import Clckwrks.IrcBot.URL      (IrcBotURL)
+import Data.Char                (isSpace)
+import Data.List                (intercalate)
+import           Data.Map       (Map)
+import qualified Data.Map       as Map
+import           Data.Set       (Set)
+import qualified Data.Set       as Set
+import Data.Word                (Word16)
+import Happstack.Server         (Response, ok, setResponseCode, toResponse)
+import HSP
+import Network.IRC.Bot          (User(..))
+import Numeric                  (readDec)
+import Text.Reform              ((++>), mapView, transformEither)
+import Text.Reform.Happstack    (reform)
+import Text.Reform.HSP.String   (inputCheckbox, inputText, label, inputSubmit, form)
+import Web.Routes               (showURL)
+
+ircBotSettings :: IrcBotURL -> IrcBotM Response
+ircBotSettings here =
+    do action    <- showURL here
+       oldConfig <- query GetIrcConfig
+       template "IrcBot Settings" () $
+                <%>
+                 <% reform (form action) "ibs" updateSettings Nothing (ircBotSettingsForm oldConfig) %>
+                </%>
+    where
+      updateSettings :: IrcConfig -> IrcBotM Response
+      updateSettings newConfig =
+          do update (SetIrcConfig newConfig)
+             template "IrcConfig updated" () $
+                      <p>IrcConfig updated</p>
+
+ircBotSettingsForm :: IrcConfig -> IrcBotForm IrcConfig
+ircBotSettingsForm IrcConfig{..} =
+     ul ((IrcConfig <$> host <*> port <*> nick <*> cp <*> user <*> channels <*> enabled) <* inputSubmit "update")
+    where
+      host     = li $ label "irc server" ++> inputText (ircHost)
+      port     = li $ label "irc port"   ++> inputText (show ircPort) `transformEither` toWord16
+      nick     = li $ label "nickname"   ++> inputText (ircNick)
+      cp       = li $ label "cmd prefix" ++> inputText (ircCommandPrefix)
+      usrnm    = li $ label "username"   ++> inputText (username ircUser)
+      hstnm    = li $ label "hostname"   ++> inputText (hostname ircUser)
+      srvrnm   = li $ label "servername" ++> inputText (servername ircUser)
+      rlnm     = li $ label "realname"   ++> inputText (realname ircUser)
+      user     = User <$> usrnm <*> hstnm <*> srvrnm <*> rlnm
+      channels = li $ label "channels (comma separated)"   ++> inputText (fromSet ircChannels) `transformEither` toSet
+      enabled  = li $ label "enable bot" ++> inputCheckbox ircEnabled
+
+      -- markup
+      li :: IrcBotForm a -> IrcBotForm a
+      li = mapView (\xml -> [<li><% xml %></li>])
+      ul :: IrcBotForm a -> IrcBotForm a
+      ul = mapView (\xml -> [<ul><% xml %></ul>])
+
+      -- transformers
+      toWord16 :: String -> Either IrcFormError Word16
+      toWord16 str =
+          case readDec str of
+            [(n,[])] -> Right n
+            _        -> (Left (CouldNotParsePort str))
+
+      fromSet :: Set String -> String
+      fromSet s = intercalate "," (Set.toList s)
+
+      toSet :: String -> Either IrcFormError (Set String)
+      toSet str =
+              Right (Set.fromList $ words' str)
+
+      words'                   :: String -> [String]
+      words' s                 =  case dropWhile isSep s of
+                                "" -> []
+                                s' -> w : words' s''
+                                      where (w, s'') =
+                                             break isSep s'
+          where
+            isSep c = isSpace c || c == ','
diff --git a/Clckwrks/IrcBot/Page/Template.hs b/Clckwrks/IrcBot/Page/Template.hs
new file mode 100644
--- /dev/null
+++ b/Clckwrks/IrcBot/Page/Template.hs
@@ -0,0 +1,64 @@
+{-# LANGUAGE FlexibleContexts, OverloadedStrings #-}
+{-# OPTIONS_GHC -F -pgmFtrhsx #-}
+module Clckwrks.IrcBot.Page.Template where
+
+import Clckwrks
+import Clckwrks.Plugin
+import Control.Monad.State (get)
+import Clckwrks.IrcBot.Monad
+import Control.Monad.Reader
+import Data.Text (Text)
+import HSP hiding (escape)
+import Happstack.Server.HSP.HTML ()
+import Web.Plugins.Core          (Plugin(..), getPluginRouteFn, getTheme)
+
+template :: ( EmbedAsChild IrcBotM headers
+            , EmbedAsChild IrcBotM body
+            ) =>
+            Text
+         -> headers
+         -> body
+         -> IrcBotM Response
+template ttl hdrs bdy =
+    do p <- plugins <$> get
+       mTheme <- getTheme p
+       (Just clckShowFn) <- getPluginRouteFn p (pluginName clckPlugin)
+       case mTheme of
+         Nothing      -> escape $ internalServerError $ toResponse $ ("No theme package is loaded." :: Text)
+         (Just theme) ->
+             do hdrXml <- fmap (map unClckChild) $ unXMLGenT $ asChild hdrs -- <%> <link rel="stylesheet" type="text/css" href=(IrcBotData "style.css") /> <% hdrs %></%>
+                bdyXml <- fmap (map unClckChild) $ unXMLGenT $ asChild bdy
+                fmap toResponse $ mapClckT f $ ClckT $ withRouteT (\f -> clckShowFn) $ unClckT $ unXMLGenT $ (_themeTemplate theme ttl hdrXml bdyXml)
+    where
+      f :: ServerPartT IO (a, ClckState) -> ReaderT IrcBotConfig (ServerPartT IO) (a, ClckState)
+      f m = ReaderT $ \_ -> m
+
+{-
+
+template :: ( EmbedAsChild IrcBotM headers
+            , EmbedAsChild IrcBotM body
+            ) =>
+            String
+         -> headers
+         -> body
+         -> IrcBotM Response
+template ttl hdrs bdy =
+    do pageTemplate <- ircBotPageTemplate <$> ask
+       fmap toResponse $ unXMLGenT $
+            pageTemplate ttl <%> <link rel="stylesheet" type="text/css" href=(DarcsData "style.css") /> <% hdrs %></%> bdy
+-}
+{-
+
+template :: ( EmbedAsChild (Clck ClckURL) headers
+            , EmbedAsChild (Clck ClckURL) body
+            ) =>
+            String
+         -> headers
+         -> body
+         -> IrcBotM Response
+template ttl hdrs bdy =
+    do pageTemplate <- ircBotPageTemplate <$> ask
+       fmap toResponse $ mapClckT lift $ unXMLGenT $
+            pageTemplate ttl hdrs bdy
+
+-}
diff --git a/Clckwrks/IrcBot/Plugin.hs b/Clckwrks/IrcBot/Plugin.hs
new file mode 100644
--- /dev/null
+++ b/Clckwrks/IrcBot/Plugin.hs
@@ -0,0 +1,123 @@
+{-# LANGUAGE RecordWildCards, FlexibleContexts, OverloadedStrings #-}
+module Clckwrks.IrcBot.Plugin where
+
+import Clckwrks
+import Clckwrks.Plugin               (clckPlugin)
+import Clckwrks.IrcBot.URL           (IrcBotURL(..), IrcBotAdminURL(..))
+import Clckwrks.IrcBot.Acid          (GetIrcConfig(..), initialIrcBotState)
+import Clckwrks.IrcBot.Monad         (IrcBotConfig(..), runIrcBotT)
+import Clckwrks.IrcBot.Route         (routeIrcBot)
+import Clckwrks.IrcBot.Types         (IrcConfig(..), emptyIrcConfig)
+import Control.Concurrent            (ThreadId, killThread)
+import Control.Monad.State           (get)
+import Data.Acid                     as Acid
+import Data.Acid.Local               (createCheckpointAndClose, openLocalStateFrom)
+import Data.Text                     (Text)
+import qualified Data.Text.Lazy      as TL
+import Data.Maybe                    (fromMaybe)
+import Data.Set                      (Set)
+import Network                       (PortID(PortNumber))
+import Network.IRC.Bot.BotMonad      (BotMonad(..))
+import Network.IRC.Bot.Core          as IRC (BotConf(..), User(..), nullBotConf, simpleBot)
+import Network.IRC.Bot.Log           (LogLevel(..), nullLogger, stdoutLogger)
+import Network.IRC.Bot.Part.Dice     (dicePart)
+import Network.IRC.Bot.Part.Hello    (helloPart)
+import Network.IRC.Bot.Part.Ping     (pingPart)
+import Network.IRC.Bot.Part.NickUser (nickUserPart)
+import Network.IRC.Bot.Part.Channels (initChannelsPart)
+import Network.IRC.Bot.PosixLogger   (posixLogger)
+import System.FilePath               ((</>))
+import Web.Plugins.Core              (Plugin(..), Plugins(..), When(..), addCleanup, addHandler, initPlugin, getConfig, getPluginRouteFn)
+import Paths_clckwrks_plugin_ircbot  (getDataDir)
+
+ircBotHandler :: (IrcBotURL -> [(Text, Maybe Text)] -> Text)
+              -> IrcBotConfig
+              -> ClckPlugins
+              -> [Text]
+              -> ClckT ClckURL (ServerPartT IO) Response
+ircBotHandler showIrcBotURL ircBotConfig plugins paths =
+    case parseSegments fromPathSegments paths of
+      (Left e)  -> notFound $ toResponse (show e)
+      (Right u) ->
+          ClckT $ withRouteT flattenURL $ unClckT $ runIrcBotT ircBotConfig $ routeIrcBot u
+    where
+      flattenURL ::   ((url' -> [(Text, Maybe Text)] -> Text) -> (IrcBotURL -> [(Text, Maybe Text)] -> Text))
+      flattenURL _ u p = showIrcBotURL u p
+
+ircBotInit :: ClckPlugins
+           -> IO (Maybe Text)
+ircBotInit plugins =
+    do (Just ircBotShowFn) <- getPluginRouteFn plugins (pluginName ircBotPlugin)
+       (Just clckShowFn) <- getPluginRouteFn plugins (pluginName clckPlugin)
+       mTopDir <- clckTopDir <$> getConfig plugins
+       let basePath  = maybe "_state"   (\td -> td </> "_state")   mTopDir -- FIXME
+           ircLogDir = maybe "_irclogs" (\td -> td </> "_irclogs") mTopDir
+       acid <- openLocalStateFrom (basePath </> "ircBot") (initialIrcBotState emptyIrcConfig)
+       addCleanup plugins Always (createCheckpointAndClose acid)
+       reconnect <- botConnect plugins acid ircLogDir
+       let ircBotConfig = IrcBotConfig { ircBotLogDirectory = ircLogDir
+                                       , ircBotState        = acid
+                                       , ircBotClckURL      = clckShowFn
+                                       , ircReconnect       = reconnect
+                                       }
+
+--       addPreProc plugins (ircBotCmd ircBotShowFn)
+       addHandler plugins (pluginName ircBotPlugin) (ircBotHandler ircBotShowFn ircBotConfig)
+       return Nothing
+
+botConnect :: Plugins theme n hook config st
+           -> Acid.AcidState (Acid.EventState GetIrcConfig)
+           -> FilePath
+           -> IO (IO ())
+botConnect plugins ircBot ircBotLogDir =
+    do ic@IrcConfig{..} <- Acid.query ircBot GetIrcConfig
+       let botConf = nullBotConf { channelLogger = Just $ posixLogger (Just ircBotLogDir) "#happs"
+                                 , IRC.host      = ircHost
+                                 , IRC.port      = PortNumber $ fromIntegral ircPort
+                                 , nick          = ircNick
+                                 , commandPrefix = ircCommandPrefix
+                                 , user          = ircUser
+                                 , channels      = ircChannels
+                                 , limits        = Just (5, 2000000)
+                                 }
+       ircParts <- initParts (channels botConf)
+       (tids, reconnect) <- simpleBot botConf ircParts
+       addCleanup plugins Always (mapM_ killThread tids)
+       return reconnect
+
+initParts :: (BotMonad m) =>
+             Set String  -- ^ set of channels to join
+          -> IO [m ()]
+initParts chans =
+    do (_, channelsPart) <- initChannelsPart chans
+       return [ pingPart
+              , nickUserPart
+              , channelsPart
+              , dicePart
+              , helloPart
+              ]
+
+addIrcBotAdminMenu :: ClckT url IO ()
+addIrcBotAdminMenu =
+    do p <- plugins <$> get
+       (Just showIrcBotURL) <- getPluginRouteFn p (pluginName ircBotPlugin)
+       let reconnectURL = showIrcBotURL (IrcBotAdmin IrcBotReconnect) []
+           settingsURL  = showIrcBotURL (IrcBotAdmin IrcBotSettings) []
+       addAdminMenu ("IrcBot", [ ("Reconnect", reconnectURL)
+                               , ("Settings", settingsURL)
+                               ])
+
+ircBotPlugin :: Plugin IrcBotURL Theme (ClckT ClckURL (ServerPartT IO) Response) (ClckT ClckURL IO ()) ClckwrksConfig [TL.Text -> ClckT ClckURL IO TL.Text]
+ircBotPlugin = Plugin
+    { pluginName       = "ircBot"
+    , pluginInit       = ircBotInit
+    , pluginDepends    = []
+    , pluginToPathInfo = toPathInfo
+    , pluginPostHook   = addIrcBotAdminMenu
+    }
+
+plugin :: ClckPlugins -- ^ plugins
+       -> Text        -- ^ baseURI
+       -> IO (Maybe Text)
+plugin plugins baseURI =
+    initPlugin plugins baseURI ircBotPlugin
diff --git a/Clckwrks/IrcBot/PreProcess.hs b/Clckwrks/IrcBot/PreProcess.hs
new file mode 100644
--- /dev/null
+++ b/Clckwrks/IrcBot/PreProcess.hs
@@ -0,0 +1,50 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Clckwrks.IrcBot.PreProcess where
+
+import Control.Monad.Trans
+import Control.Applicative
+import Clckwrks (ClckT, ClckState)
+import Clckwrks.IrcBot.URL
+import Data.Attoparsec.Text
+import           Data.Text (Text)
+import           Data.Text.Lazy.Builder (Builder)
+import qualified Data.Text.Lazy.Builder as B
+import           Text.Blaze.Html ((!), toValue)
+import qualified Text.Blaze.Html5 as H
+import qualified Text.Blaze.Html5.Attributes as A
+import Text.Blaze.Html.Renderer.Text (renderHtml)
+import Web.Routes (showURL)
+
+parseAttr :: Text -> Parser ()
+parseAttr name =
+    do skipMany space
+       stringCI name
+       skipMany space
+       char '='
+       skipMany space
+
+width :: Parser H.Attribute
+width =
+    A.width . H.toValue <$> (parseAttr "width" *> (decimal :: Parser Integer))
+
+height :: Parser H.Attribute
+height =
+    A.height . H.toValue <$> (parseAttr "height" *> (decimal :: Parser Integer))
+{-
+parseCmd :: Parser (MediumId, [H.Attribute])
+parseCmd =
+    (,) <$> (parseAttr "id" *> (MediumId <$> decimal))
+        <*> (many $ choice [ width, height ])
+-} 
+ircBotCmd :: (Monad m) => (IrcBotURL -> [(Text, Maybe Text)] -> Text) -> Text -> ClckT url m Builder
+ircBotCmd showURLFn txt =
+    return $ B.fromString "ircBot does not currently support any markup commands."
+{-
+    do let mi = parseOnly parseCmd txt
+       case mi of
+         (Left e) ->
+               return $ B.fromString e -- FIXME: format the error more nicely or something?
+         (Right (mid, attrs)) ->
+             do let u = toValue $ showURLFn (GetMedium mid) []
+                return $ B.fromLazyText $ renderHtml $ foldr (\attr tag -> tag ! attr) H.img (A.src u : attrs)
+-}
diff --git a/Clckwrks/IrcBot/Route.hs b/Clckwrks/IrcBot/Route.hs
new file mode 100644
--- /dev/null
+++ b/Clckwrks/IrcBot/Route.hs
@@ -0,0 +1,31 @@
+module Clckwrks.IrcBot.Route where
+
+import Control.Applicative             ((<$>))
+import Control.Monad.Reader            (ask)
+import Clckwrks                        (Clck, Role(..), requiresRole_)
+import Clckwrks.IrcBot.Monad           (IrcBotM, IrcBotConfig(..))
+import Clckwrks.IrcBot.Page.IrcLog     (ircLog)
+import Clckwrks.IrcBot.Page.IrcLogs    (ircLogs)
+import Clckwrks.IrcBot.Page.Reconnect  (ircReconnectPage)
+import Clckwrks.IrcBot.Page.Settings   (ircBotSettings)
+import Clckwrks.IrcBot.URL             (IrcBotURL(..), IrcBotAdminURL(..))
+import qualified Data.Set              as Set
+import Happstack.Server                (Response, toResponse, notFound)
+
+checkAuth :: IrcBotURL -> IrcBotM IrcBotURL
+checkAuth url =
+    case url of
+      IrcBotAdmin {} ->
+          do showFn <- ircBotClckURL <$> ask
+             requiresRole_ showFn (Set.singleton Administrator) url
+      _ -> return url
+
+
+routeIrcBot :: IrcBotURL -> IrcBotM Response
+routeIrcBot unsecureURL =
+    do url <- checkAuth unsecureURL
+       case url of
+         IrcLogs                       -> ircLogs
+         IrcLog fp                     -> ircLog fp
+         (IrcBotAdmin IrcBotReconnect) -> ircReconnectPage url
+         (IrcBotAdmin IrcBotSettings)  -> ircBotSettings url
diff --git a/Clckwrks/IrcBot/Types.hs b/Clckwrks/IrcBot/Types.hs
new file mode 100644
--- /dev/null
+++ b/Clckwrks/IrcBot/Types.hs
@@ -0,0 +1,54 @@
+{-# LANGUAGE DeriveDataTypeable, GeneralizedNewtypeDeriving, TemplateHaskell, TypeFamilies #-}
+module Clckwrks.IrcBot.Types
+    ( IrcConfig(..)
+    , User(..)
+    , emptyIrcConfig
+    ) where
+
+import Data.Data             (Data, Typeable)
+import Data.Word             (Word16)
+import Data.IxSet            (Indexable(..), ixSet, ixFun)
+import Data.SafeCopy         (Migrate(..), SafeCopy, base, deriveSafeCopy, extension)
+import Data.Set              (Set, empty)
+import Data.Text             (Text)
+import Network.IRC.Bot.Types (User(..))
+import Web.Routes            (PathInfo(..))
+
+data IrcConfig_0 = IrcConfig_0
+    { ircHost_0          :: String
+    , ircPort_0          :: Word16
+    , ircNick_0          :: String
+    , ircCommandPrefix_0 :: String
+    , ircUser_0          :: User
+    , ircChannels_0      :: Set String
+    }
+    deriving (Eq, Ord, Read, Show, Data, Typeable)
+$(deriveSafeCopy 0 'base ''User)
+$(deriveSafeCopy 0 'base ''IrcConfig_0)
+
+data IrcConfig = IrcConfig
+    { ircHost          :: String     -- ^ IRC server
+    , ircPort          :: Word16     -- ^ port (usually 6667)
+    , ircNick          :: String     -- ^ irc nick
+    , ircCommandPrefix :: String     -- ^ prefix for bot commands
+    , ircUser          :: User       -- ^ irc 'User'
+    , ircChannels      :: Set String -- ^ channels to join on connect
+    , ircEnabled       :: Bool       -- ^ enable the bot
+    }
+    deriving (Eq, Ord, Read, Show, Data, Typeable)
+$(deriveSafeCopy 1 'extension ''IrcConfig)
+
+instance Migrate IrcConfig where
+    type MigrateFrom IrcConfig = IrcConfig_0
+    migrate (IrcConfig_0 h p n cp u cs) = (IrcConfig h p n cp u cs True)
+
+emptyIrcConfig :: IrcConfig
+emptyIrcConfig = IrcConfig
+    { ircHost          = ""
+    , ircPort          = 0
+    , ircNick          = ""
+    , ircCommandPrefix = ""
+    , ircUser          = User "" "" "" ""
+    , ircChannels      = Data.Set.empty
+    , ircEnabled       = False
+    }
diff --git a/Clckwrks/IrcBot/URL.hs b/Clckwrks/IrcBot/URL.hs
new file mode 100644
--- /dev/null
+++ b/Clckwrks/IrcBot/URL.hs
@@ -0,0 +1,18 @@
+{-# LANGUAGE DeriveDataTypeable, TemplateHaskell #-}
+module Clckwrks.IrcBot.URL where
+
+import Data.Data     (Data, Typeable)
+import Web.Routes.TH (derivePathInfo)
+
+data IrcBotAdminURL
+    = IrcBotReconnect
+    | IrcBotSettings
+      deriving (Eq, Ord, Read, Show, Data, Typeable)
+$(derivePathInfo ''IrcBotAdminURL)
+
+data IrcBotURL
+    = IrcLogs
+    | IrcLog FilePath
+    | IrcBotAdmin IrcBotAdminURL
+      deriving (Eq, Ord, Read, Show, Data, Typeable)
+$(derivePathInfo ''IrcBotURL)
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c)2012, Jeremy Shaw, SeeReason Partners LLC
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Jeremy Shaw nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,13 @@
+#!/usr/bin/env runghc
+
+module Main where
+
+import Distribution.Simple
+import Distribution.Simple.Program
+
+trhsxProgram = simpleProgram "trhsx"
+
+main :: IO ()
+main = defaultMainWithHooks simpleUserHooks {
+         hookedPrograms = [trhsxProgram]
+       }
diff --git a/clckwrks-plugin-ircbot.cabal b/clckwrks-plugin-ircbot.cabal
new file mode 100644
--- /dev/null
+++ b/clckwrks-plugin-ircbot.cabal
@@ -0,0 +1,66 @@
+Name:                clckwrks-plugin-ircbot
+Version:             0.3.0
+Synopsis:            ircbot plugin for clckwrks
+Homepage:            http://clckwrks.com/
+License:             BSD3
+License-file:        LICENSE
+Author:              Jeremy Shaw
+Maintainer:          Jeremy Shaw <jeremy@n-heptane.com>
+Copyright:           2012 Jeremy Shaw, SeeReason Partners LLC
+Category:            Clckwrks
+Build-type:          Custom
+Cabal-version:       >=1.6
+Data-Files:
+    data/style.css
+
+source-repository head
+    type:     darcs
+    subdir:   clckwrks-plugin-ircbot
+    location: http://hub.darcs.net/stepcut/clckwrks
+
+Library
+  Build-tools:
+    trhsx
+
+  Exposed-modules:
+    Clckwrks.IrcBot
+    Clckwrks.IrcBot.Acid
+    Clckwrks.IrcBot.Monad
+    Clckwrks.IrcBot.Page.IrcLog
+    Clckwrks.IrcBot.Page.IrcLogs
+    Clckwrks.IrcBot.Page.Reconnect
+    Clckwrks.IrcBot.Page.Settings
+    Clckwrks.IrcBot.Page.Template
+    Clckwrks.IrcBot.Plugin
+    Clckwrks.IrcBot.PreProcess
+    Clckwrks.IrcBot.Route
+    Clckwrks.IrcBot.Types
+    Clckwrks.IrcBot.URL
+    Paths_clckwrks_plugin_ircbot
+
+  Build-depends:
+    base                    < 5,
+    acid-state             >= 0.7,
+    attoparsec             == 0.10.*,
+    blaze-html             == 0.5.*,
+    clckwrks               == 0.13.*,
+    containers             >= 0.4 && < 0.6,
+    directory              >= 1.1 && < 1.3,
+    filepath               >= 1.2 && < 1.4,
+    happstack-server       >= 7.0 && < 7.2,
+    happstack-hsp          == 7.1.*,
+    hsp                    == 0.7.*,
+    ircbot                 >= 0.5.2 && < 0.6,
+    ixset                  == 1.0.*,
+--    magic                  == 1.0.*,
+    mtl                    >= 2.0 && < 2.3,
+    network                >= 2.3 && < 2.5,
+    reform                 == 0.1.*,
+    reform-happstack       == 0.1.*,
+    reform-hsp             >= 0.1.1 && < 0.2,
+    safecopy               >= 0.6,
+    text                   == 0.11.*,
+    web-plugins            == 0.1.*,
+    web-routes             == 0.27.*,
+    web-routes-th          >= 0.21
+
diff --git a/data/style.css b/data/style.css
new file mode 100644
--- /dev/null
+++ b/data/style.css
@@ -0,0 +1,10 @@
+ul#media-gallery
+{
+    list-style-type: none;
+}
+
+#media-gallery li
+{
+    display: inline;
+    padding-right: 1em;
+}
