diff --git a/EXAMPLE b/EXAMPLE
new file mode 100644
--- /dev/null
+++ b/EXAMPLE
@@ -0,0 +1,23 @@
+BORING COMMANDS:
+
+> ls
+> cd
+> pwd
+> cd .*
+> cp [^-].*
+> mv [^-].*
+> python
+
+FILE COMPARISON:
+
+Side-by-side diff
+> diff -y .*
+
+Standard diff
+> diff [^-].*
+
+FILE MANAGEMENT:
+
+Make a directory and all its ancestors
+> mkdir -p .*
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2009 Max Rabkin
+
+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/README b/README
new file mode 100644
--- /dev/null
+++ b/README
@@ -0,0 +1,30 @@
+HISTORIAN
+---------------------------------------------------------------------------------
+
+Historian extracts interesting commands from your shell history and adds them to
+a text file. "Interesting" means not matching any regular expression in the
+file. This allows one to keep a textual "database" of commands.
+
+Never again say "I wish I remembered what arguments to give wibble to make it
+flibber the gibbet" three months after you looked it up.
+
+---------------------------------------------------------------------------------
+
+WARNING: when historian is run, it truncates ~/.bash_history
+
+When you run historian, it looks for lines in ~/interesting_history that
+starts with start with a greater-than symbol. These lines are interpreted as
+regexes. It then looks in ~/.bash_history for lines that don't match any of
+the regexes, appends them to ~/interesting_history and opens your favourite
+editor to edit the file.
+
+You can then generalise the commands into regexes so that subsequent runs of
+historian will ignore similar ones. You can delete commands, but if they appear
+in your history again, historian will again consider them "interesting" (it's
+for this reason that historian truncates the history after it runs).
+
+Some of the regexes will inevitably be uninteresting, but you should generalise
+them so that they don't show up again. Interesting commands should be
+categorised and described, so that you can refer to them later.
+
+An example interesting_history is contained in the EXAMPLE file.
diff --git a/Setup.lhs b/Setup.lhs
new file mode 100644
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,4 @@
+#! /usr/bin/env runhaskell> import Distribution.Simple> main = defaultMain#! /usr/bin/env runhaskell
+
+> import Distribution.Simple
+> main = defaultMain
diff --git a/historian.cabal b/historian.cabal
new file mode 100644
--- /dev/null
+++ b/historian.cabal
@@ -0,0 +1,35 @@
+Name:           historian
+Version:        0.0
+Cabal-Version:  >= 1.2
+Synopsis:       Extract the interesting bits from shell history
+Description:    Extract interesting commands and add them to a text file.
+                \"Interesting\" means not matching any regular expression in the
+                file. This allows one to keep a textual database of commands.
+
+                Never again say \"I wish I remembered what arguments to give
+                wibble to make it flibber the gibbet\" three months after you
+                looked it up.
+                
+                WARNING: historian truncates ~/.bash_history.
+License:        BSD3
+License-file:   LICENSE
+Author:         Max Rabkin
+Maintainer:     max.rabkin@gmail.com
+Stability:      Experimental
+Build-Type:     Simple
+Category:       Console
+
+Extra-Source-Files: EXAMPLE README
+
+Executable historian
+    Build-Depends:  base >= 3 && < 5,
+                    process >= 1 && < 2,
+                    directory >= 1 && < 2,
+                    filepath >= 1 && < 2,
+                    regex-posix >= 0.94 && < 0.95,
+                    regex-compat >= 0.92 && < 0.93,
+                    containers >= 0.1 && < 0.3
+
+    Executable:     historian
+    Main-Is:        historian.hs
+
diff --git a/historian.hs b/historian.hs
new file mode 100644
--- /dev/null
+++ b/historian.hs
@@ -0,0 +1,49 @@
+import qualified Data.Map as M
+import Data.List (group, isPrefixOf, sort)
+import Text.Regex
+import Text.Regex.Posix
+import Data.Char (isSpace)
+import Control.Arrow (second)
+import Control.Applicative ((<$>), liftA2)
+import System.Cmd (rawSystem)
+import System.FilePath ((</>))
+import System.Directory (removeFile)
+import System.Environment (getArgs, getEnv)
+
+type PatternDB = M.Map String Pattern
+type Pattern = String
+
+extractPatterns :: String -> PatternDB
+extractPatterns =   --M.map makeRegex
+                    M.fromListWith (\x y -> x ++ '|':y) .
+                    map (second (\x -> "(" ++ x ++ ")") . splitCmd . tail) .
+                    filter (">" `isPrefixOf`) .
+                    lines
+
+splitCmd :: String -> (String, String)
+splitCmd = second (dropWhile isSpace) . break isSpace . dropWhile isSpace
+
+extractInteresting :: PatternDB -> [String] -> [String]
+extractInteresting db = filter (not . liftA2 (||) (`inDB` db) ("#" `isPrefixOf`))
+
+inDB :: String -> PatternDB -> Bool
+cmd `inDB` db = let (prog, args) = splitCmd cmd in
+                    case M.lookup prog db of
+                        Just pat -> args =~ pat
+                        Nothing -> False
+
+main = do
+        homeDir <- catch (getEnv "HOME") (const . return $ ".")
+        editor <- catch (getEnv "EDITOR") (const . return $ "nano")
+        args <- getArgs
+        let fn = case args of
+                    [] -> homeDir </> "interesting_history"
+                    [x] -> x
+
+        let histfile = homeDir </> ".bash_history"
+        boring <- extractPatterns <$> readFile fn
+        history <- readFile $ histfile
+        let interesting = map head . group . sort . extractInteresting boring . lines $ history
+        boring `seq` (appendFile fn . unlines . map ("> " ++) $ interesting)
+        interesting `seq` writeFile histfile "# history has been rewritten"
+        rawSystem editor [fn]
