diff --git a/FeedCLI.hs b/FeedCLI.hs
new file mode 100644
--- /dev/null
+++ b/FeedCLI.hs
@@ -0,0 +1,180 @@
+{-
+
+TODO / ideas:
+- try using it for something :)
+- --watch mode that runs feed-cli at a given interval
+- attachments
+- when adding an item to a non-existant feed, create a new feed file?
+- use -c"command --arg" instead of --pipe-mode so we can get more information about a command being run?
+- delete oldest feeds instead of front feeds
+- implement very simple version? ./feed-cli title "this is description" or so?
+- clean up parameter checks, -D others
+- --format=atom, rss 
+- test it against feed verifyer - http://feedvalidator.org/
+
+DONE:
+- --pipe-mode = stdin becomes description, subject is command run, time, return value? echo items to screen?  or maybe use -c"command" for more control?
+- clean up num parser for better error
+- input & output from files
+- description input from file
+- --update file instead of -i and -o - if -i and -o are the same, or if they use -u then create a temp file and update.
+--limit=10 delete the oldest feeds so that only 10 remain - could stand alone as a command?
+-}
+module Main where
+
+import Text.RSS.Syntax (RSS (..), RSSChannel (..), RSSItem (..), nullItem, nullRSS)
+import Text.RSS.Export (xmlRSS)
+import Text.RSS.Import (elementToRSS)
+
+import Text.XML.Light.Output (showTopElement)
+import Text.XML.Light.Input (parseXMLDoc)
+
+import System.Environment(getArgs)
+import System.Locale (defaultTimeLocale)
+import System.IO (openTempFile, openFile, hGetContents
+                 , hClose, hPutStr, IOMode(..), stdin)
+import System.Directory (renameFile)
+import System.Directory(doesFileExist)
+
+import Data.Maybe(fromJust, fromMaybe)
+import Control.Monad(when)
+import Data.Time.Clock (getCurrentTime, UTCTime)
+import Data.Time.Format (formatTime)
+
+import Options (Options(..), LimitType (..), DescType (..), parseOpts)
+
+------------------------------------------------------------
+-- * Helpers
+------------------------------------------------------------
+
+inetFormat             :: UTCTime -> String
+inetFormat t            = formatTime defaultTimeLocale "%a, %d %b %Y %T GMT" t
+
+notImplemented :: String -> a
+notImplemented a = error $ a ++ " not implemented."
+
+-- * Transformers
+addFeedItem :: RSS -> RSSItem -> RSS
+addFeedItem feed item
+    = let oldChannel = rssChannel feed
+          oldItems = rssItems oldChannel in
+       feed{rssChannel=oldChannel{rssItems=item:oldItems}}
+
+------------------------------------------------------------
+-- * IO
+------------------------------------------------------------
+
+writeRSS :: Maybe FilePath -- If they haven't provided an argument, stdout.
+         -> RSS
+         -> Bool -- ^create a temporary file?
+         -> IO ()
+writeRSS (Just outFile) rss createTemp = do
+  (fileToWrite, h) <- if createTemp
+                       then openTempFile "/tmp" ("rssGenTemp.xml") -- fix: portable?
+                       else do h' <- openFile outFile WriteMode
+                               return (outFile, h')
+  hPutStr h (showTopElement $ xmlRSS rss)
+  hClose h
+  when createTemp (renameFile fileToWrite outFile)
+
+writeRSS Nothing rss _ = putStrLn $  showTopElement $ xmlRSS rss
+
+-- Throw some sane exceptions open file fail, etc.  Creates basic empty feed if it doesn't exist
+readRSS :: FilePath -> IO RSS
+readRSS inFile = do
+  exists <- doesFileExist inFile
+  when (not exists) (
+     (error ("input file doesn't exist: " ++ inFile
+          ++ "\n(hint, use ./feed-cli new-feed)" ))
+   )
+  xmlStr <- readFile inFile
+  let mXmlDoc = parseXMLDoc xmlStr
+  return $ fromJust $ elementToRSS (fromJust mXmlDoc)
+
+
+------------------------------------------------------------
+-- * Handling Commands & Main
+------------------------------------------------------------
+
+handleCommands :: Options
+               -> [String]
+               -> IO ()
+handleCommands opts command = do
+    case command of
+      ["new-feed"] -> doNewFeed opts
+      ["new-item"] -> doNewItem opts
+      c -> error ("unknown command: " ++ (show c))
+
+------------------------------------------------------------
+
+-- |Take the given number of elements from the front. Don't compare time yet.
+limitItems :: RSS -- ^ the feed to filter
+           -> Integer -- ^Max items
+           -> RSS -- ^the feed with max items
+limitItems rss limit
+    = let oldChannel = rssChannel rss
+          oldItems   = rssItems oldChannel
+          newItems   = take (fromIntegral limit) oldItems
+       in rss{rssChannel=oldChannel{rssItems=newItems}}
+
+doNewItem :: Options -> IO ()
+doNewItem opts = do
+  let mOutFile = optOutput opts
+  let feedTitle = optTitle opts
+
+  let mLink = optLink opts
+  -- fix: These will become more flexible when we have more commands we can do:
+  let inFile = fromMaybe (error "infile required") (optInput opts)
+  let createTemp = Just inFile == mOutFile
+  let mLimit = optLimit opts
+
+  feedDescription <- getDescription (optDescription opts) (optPreDesc opts)
+
+  readFeed <- readRSS inFile
+  time <- getCurrentTime
+  let emptyItem = nullItem feedTitle
+  let newItem=emptyItem{rssItemPubDate=Just (inetFormat time)
+                       ,rssItemDescription = Just feedDescription
+                       ,rssItemLink = mLink
+                       }
+  let feedWithItem = addFeedItem readFeed newItem
+  let newFeed
+       = case mLimit of
+           NoLimit  -> addFeedItem readFeed newItem
+           KeepLimit -> limitItems feedWithItem
+                          (fromIntegral $ length $ rssItems $ rssChannel readFeed)
+           NumLimit lim -> limitItems feedWithItem lim
+  writeRSS mOutFile newFeed createTemp
+
+
+getDescription :: DescType
+               -> Bool -- add <pre>description</pre> tags?
+               -> IO String
+getDescription oDescription pre = do
+  desc <- case oDescription of
+            DescCLI s -> return s
+            DescFile d  -> readFile d
+            DescSTDIN -> hGetContents stdin
+  return $ if pre then ("<pre>" ++ desc ++ "</pre>") else desc
+
+
+
+doNewFeed :: Options -> IO ()
+doNewFeed opts = do
+  let mOutFile = optOutput opts
+  let title = optTitle opts
+  desc <- getDescription (optDescription opts) (optPreDesc opts)
+  let mLink = optLink opts
+  let link = fromMaybe (error "--link required") mLink
+
+  let newRSS = nullRSS title link
+  let newChannel = (rssChannel newRSS){rssDescription = desc}
+
+  writeRSS mOutFile newRSS{rssChannel=newChannel} False
+
+------------------------------------------------------------
+
+main :: IO ()
+main = do args <- getArgs
+          (opts, command) <- parseOpts args
+          handleCommands opts command
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,31 @@
+(c) 2008 Isaac Potoczny-Jones
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+2. 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.
+
+3. Neither the name of the author nor the names of his contributors
+may be used to endorse or promote products derived from this software
+without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE 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 AUTHORS 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/Options.hs b/Options.hs
new file mode 100644
--- /dev/null
+++ b/Options.hs
@@ -0,0 +1,105 @@
+module Options where
+
+import System.Console.GetOpt
+import Data.Maybe(fromMaybe)
+
+data LimitType = NumLimit Integer -- limit feed size to given number
+               | NoLimit -- don't limit
+               | KeepLimit -- limit feed size to current size (during add-item)
+     deriving Show
+
+data DescType = DescFile FilePath -- means -Dfoo
+              | DescCLI String -- means -dFoo
+              | DescSTDIN -- means --pipe-mode
+     deriving Show
+
+data Options = Options
+ { optVerbose     :: Bool
+ , optShowVersion :: Bool
+ , optPreDesc     :: Bool
+ , optOutput      :: Maybe FilePath
+ , optInput       :: Maybe FilePath
+ , optDescription :: DescType
+ , optLimit       :: LimitType
+ , optTitle       :: String
+ , optLink        :: Maybe String
+ } deriving Show
+
+defaultOptions :: Options
+defaultOptions    = Options
+ { optVerbose     = False
+ , optShowVersion = False
+ , optPreDesc     = False
+ , optOutput      = Nothing
+ , optInput       = Nothing
+ , optLimit       = NoLimit
+ , optTitle       = ""
+ , optDescription = DescCLI ""
+ , optLink        = Nothing
+ }
+
+options :: [OptDescr (Options -> Options)]
+options =
+ [ Option ['v']     ["verbose"]
+     (NoArg (\ opts -> opts { optVerbose = True }))
+     "chatty output on stderr"
+ , Option ['V','?'] ["version"]
+     (NoArg (\ opts -> opts { optShowVersion = True }))
+     "show version number"
+
+ , Option ['p'] ["pre"]
+     (NoArg (\ opts -> opts { optPreDesc = True }))
+     "add <pre> tag to description for html readers"
+
+ , Option [] ["pipe-mode"]
+     (NoArg (\ opts -> opts { optDescription = DescSTDIN }))
+     "description pipe mode, suitable for piping commands into, or -d or -D"
+
+ , Option ['D']     ["description-file"]
+     (ReqArg ((\ f opts -> opts { optDescription = DescFile f })) "FILE")
+     "read the description from given file. or -d or --pipe-mode"
+
+ , Option ['d']     ["description"]
+         (ReqArg (\ t opts -> opts { optDescription = DescCLI t }) "description")
+         "feed item description from command line. or -D or --pipe-mode"
+
+ , Option ['o']     ["output"]
+     (ReqArg ((\ f opts -> opts { optOutput = Just f })) "FILE")
+     "output to this file, or -u"
+
+ , Option ['u']     ["update-file"]
+    (ReqArg ((\ f opts -> opts { optInput = Just f
+                               , optOutput = Just f })) "FILE")
+    "update-file FILE"
+ , Option []     ["limit"]
+    (OptArg ((\ m opts -> opts { optLimit = 
+                                   case m of
+                                     Nothing -> KeepLimit
+                                     Just l  -> NumLimit (fromMaybe (error ("--limit= expecting number, got " ++ show l)) (readInteger l))
+                               }))
+            "NUM")
+    "limit=NUM"
+ , Option ['i']     ["input"]
+     (ReqArg ((\ f opts -> opts { optInput = Just f })) "FILE")
+     "input FILE"
+ , Option ['l']     ["link"]
+     (ReqArg ((\ f opts -> opts { optLink = Just f })) "URL")
+     "link http://foo.com"
+ , Option ['t']     ["title"]
+         (ReqArg (\ t opts -> opts { optTitle = t }) "title")
+         "feed item title"
+
+ ]
+
+readInteger :: String -> Maybe Integer
+readInteger s =
+  case reads s of
+    [(n, "")] -> Just n
+    _         -> Nothing
+
+parseOpts :: [String] -> IO (Options, [String])
+parseOpts argv =
+   case getOpt Permute options argv of
+      (o,n,[]  ) -> return (foldl (flip id) defaultOptions o, n)
+      (_,_,errs) -> ioError (userError (concat errs ++ usageInfo header options))
+  where header = "Usage: ic [OPTION...] files..."
diff --git a/README b/README
new file mode 100644
--- /dev/null
+++ b/README
@@ -0,0 +1,40 @@
+See my announcement:
+http://www.syntaxpolice.org/index.php/?q=node/425
+
+Synopsis:
+
+This program generates [RSS 2.0 http:\/\/www.rssboard.org\/rss-2-0-1-rv-6] 
+feeds based on command line arguments.  Use it to create and update feeds
+from shell scripts, build scripts, cron jobs, CGIs, or other programs instead
+of using an RSS or Atom library.
+
+Commands and args:
+
+new-item: -iFILE (required, or -u): The feed to add this item to
+          -oFILE (optional): otherwise, stdout
+          -tTitle (required)
+          -dDescription (required, or -D, or --pipe-mode)
+          --pipe-mode: read description from STDIN
+          -lhttp://example.com (optional): Item link
+          -DFILE: read description from a file
+          -uFILE (optional): Update this file, not valid with -i or -o. Same as -iFILE == -oFILE
+          --limit (optional): Keep the number of items fixed. Drop the last item when adding a new item. Doesn't compare dates yet. Taken from the front since that's how we add them.
+          --limit=NUM (optional): keep only the last NUM items.
+
+new-feed: -oFILE (optional): otherwise, stdout
+          -tTitle (required): feed title
+          -dDescription (required): feed description
+          -lhttp://example.com (required): Feed link
+
+EXAMPLES:
+
+See also the patterns directory.
+
+# create an empty feed:
+./feed-cli new-feed -tTitleOfFeed -d"Feed Description" -o/tmp/feed.xml  -lhttp://www.syntaxpolice.org
+
+# add an item to that feed 
+./feed-cli new-item -t"entry of the day" -d"This is a description of <b>this feed item</b>." -u/tmp/feed.xml  -lhttp://www.syntaxpolice.org
+
+# pipe a command into a feed item
+ls -l | ./feed-cli new-item --pipe-mode --pre -t"another entry of the day" -u/tmp/feed.xml  -lhttp://www.syntaxpolice.org
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,4 @@
+#!/usr/bin/env runghc
+
+import Distribution.Simple
+main = defaultMain
diff --git a/examples/getURL.sh b/examples/getURL.sh
new file mode 100644
--- /dev/null
+++ b/examples/getURL.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+# use like ./getURL http://www.galois.com
+# This pattern fetches a reguar web page using wget, and generates a
+# feed entry for it.
+
+FEEDFILE=/tmp/pageFeed.xml
+
+# If the feed doesn't already exist, generate it with something like this:
+# ./feed-cli new-feed -t"Page Updates" -o$FEEDFILE -lhttp://www.example.com
+
+echo $1
+wget $1 -O outfile.html
+
+./dist/build/feed-cli/feed-cli new-item -u$FEEDFILE -t"[$1]  @ `date`" -Doutfile.html -l$1
+
+rm outfile.html
diff --git a/examples/piper.sh b/examples/piper.sh
new file mode 100644
--- /dev/null
+++ b/examples/piper.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+CMD="head /etc/dictionaries-common/words"
+#CMD="./setup build"
+
+echo "<li>Command: $CMD" > cmdOut
+echo "<li>Executed by $USER @ `date`" >> cmdOut
+echo "<li>Command completed with return code: $?" >> cmdOut
+echo "<li><b>Command output follows:</b>" >> cmdOut
+
+
+echo '<pre>' >> cmdOut
+$CMD>>cmdOut
+echo '</pre>' >> cmdOut
+
+./dist/build/feed-cli/feed-cli new-item -u/tmp/piper.xml -t"[$?] > $CMD @ `date`" -DcmdOut
+
+rm cmdOut
+
+#./dist/build/feed-cli/feed-cli new-feed -o/tmp/piper.xml -t"Output from your commands" -lhttp://www.syntaxpolice.org
diff --git a/examples/piper2.sh b/examples/piper2.sh
new file mode 100644
--- /dev/null
+++ b/examples/piper2.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+CMD="head /etc/dictionaries-common/words"
+#CMD="./setup build"
+
+$CMD | ./dist/build/feed-cli/feed-cli new-item -u/tmp/piper.xml -t"[$?] > $CMD @ `date`" --pipe-mode --pre
diff --git a/feed-cli.cabal b/feed-cli.cabal
new file mode 100644
--- /dev/null
+++ b/feed-cli.cabal
@@ -0,0 +1,25 @@
+Name: feed-cli
+Version: 2008.5.3
+Author: Isaac Potoczny-Jones <ijones@syntaxpolice.org>
+Maintainer: Isaac Potoczny-Jones <ijones@syntaxpolice.org>
+Copyright: (c) Isaac Potoczny-Jones, 2008
+Category: Web, Text
+HomePage: http://www.syntaxpolice.org/darcs_repos/feed-cli
+License: BSD3
+License-File: LICENSE
+build-depends: feed, xml, base, time, old-locale, old-time, directory
+data-files: README, examples/getURL.sh,  examples/piper2.sh,  examples/piper.sh
+build-type: Simple
+Synopsis: A simple command line interface for creating and updating feeds like RSS
+Description:
+ This program generates RSS 2.0 (http://www.rssboard.org/rss-2-0-1-rv-6)
+ feeds based on command line arguments.  Use it to easily create and update
+ feeds from shell scripts, build scripts, cron jobs, CGIs, or other programs
+ instead of using an RSS or Atom library.
+ .
+ eg: feed-cli new-item -t"entry of the day" -d"This is a description..." -u/tmp/feed.xml  -lhttp://www.syntaxpolice.org
+
+Executable: feed-cli
+main-is: FeedCLI.hs
+other-modules: Options
+ghc-options: -Wall
