diff --git a/ethereum-analyzer-cli.cabal b/ethereum-analyzer-cli.cabal
--- a/ethereum-analyzer-cli.cabal
+++ b/ethereum-analyzer-cli.cabal
@@ -1,5 +1,5 @@
 name:                ethereum-analyzer-cli
-version:             3.1.0
+version:             3.2.0
 synopsis:            A CLI frontend for ethereum-analyzer.
 homepage:            https://github.com/zchn/ethereum-analyzer
 license:             Apache-2.0
@@ -21,7 +21,7 @@
 source-repository this
   type:     git
   location: https://github.com/zchn/ethereum-analyzer
-  tag:      v3.1.0
+  tag:      v3.2.0
   subdir:   ethereum-analyzer-cli
 
 library
@@ -33,18 +33,24 @@
                      , ethereum-analyzer
                      , ethereum-analyzer-deps
                      , exceptions
+                     , filepath
                      , hexstring
+                     , hoopl
                      , http-conduit
                      , json-rpc
                      , monad-logger
                      , mtl
+                     , optparse-applicative
+                     , optparse-text
                      , protolude
                      , text
+                     , time
                      , tostring
                      , unordered-containers
                      , vector
                        
-  exposed-modules:     Ethereum.Executable.BytecodeVisMain
+  exposed-modules:     Ethereum.Executable.Analyze
+                       Ethereum.Executable.BytecodeVisMain
                        Ethereum.Executable.DumpCodeMain
   other-modules:       Ethereum.Jsonrpc.Client                       
   ghc-options:         -Wall
@@ -57,14 +63,8 @@
 executable ea-analyze
   main-is:             AnalyzeMain.hs
   build-depends:       base >= 4 && < 5
-                     , ethereum-analyzer
                      , ethereum-analyzer-cli
-                     , ethereum-analyzer-deps
-                     , monad-logger
-                     , optparse-applicative
-                     , optparse-text
                      , protolude
-                     , text
   ghc-options:         -Wall
   hs-source-dirs:      exec_src
   buildable:           True
diff --git a/exec_src/AnalyzeMain.hs b/exec_src/AnalyzeMain.hs
--- a/exec_src/AnalyzeMain.hs
+++ b/exec_src/AnalyzeMain.hs
@@ -4,49 +4,7 @@
 
 import Protolude
 
-import qualified Data.Text as T
-
-import Ethereum.Analyzer.Debug
-import Ethereum.Analyzer.Solidity hiding (value)
-
-import Options.Applicative
-import Options.Applicative.Text
-
-data AnalyzeFlags = AnalyzeFlags
-  { astJson :: Text
-  , debug :: Bool
-  } deriving (Eq, Show)
-
-analyzeFlags :: Parser AnalyzeFlags
-analyzeFlags =
-  AnalyzeFlags <$>
-  textOption
-    (long "astJson" <> value "" <> metavar "PATH" <>
-     help "Path to the ast-json file.") <*>
-  switch (long "debug" <> help "Whether to print debug info")
+import Ethereum.Executable.Analyze
 
 main :: IO ()
-main = analyze =<< execParser opts
-  where
-    opts =
-      info
-        (analyzeFlags <**> helper)
-        (fullDesc <>
-         progDesc (toS ("Analyze the contract specified at PATH" :: Text)) <>
-         header
-           (toS ("ea-analyze - CLI interface for ethereum-analyzer" :: Text)))
-
-analyze :: AnalyzeFlags -> IO ()
-analyze flags@AnalyzeFlags {astJson = theAstJson, debug = debug} = do
-  when debug $ putText $ show flags
-  content <-
-    if theAstJson == "" || theAstJson == "-"
-      then getContents
-      else readFile $ toS theAstJson
-  case decodeContracts content of
-    Right contracts -> do
-      when debug $ pprintContracts contracts
-      putText "Findings: \n"
-      putText ("\n" `T.intercalate` concatMap findingsFor contracts)
-    Left err -> putText err
-  return ()
+main = analyzeMain
diff --git a/src/Ethereum/Executable/Analyze.hs b/src/Ethereum/Executable/Analyze.hs
new file mode 100644
--- /dev/null
+++ b/src/Ethereum/Executable/Analyze.hs
@@ -0,0 +1,98 @@
+module Ethereum.Executable.Analyze
+  ( analyzeMain
+  ) where
+
+import Protolude hiding ((<.>))
+
+import Compiler.Hoopl (runSimpleUniqueMonad)
+
+import Data.Time.Clock
+import Data.Time.Format
+
+import Ethereum.Analyzer.Debug
+import Ethereum.Analyzer.Solidity hiding (value)
+import Ethereum.Analyzer.Util
+
+import Options.Applicative
+import Options.Applicative.Text
+
+import System.Directory
+import System.FilePath
+
+import qualified Data.Text as T
+
+data AnalyzeFlags = AnalyzeFlags
+  { astJson :: Text
+  , workDir :: Text
+  , debug :: Bool
+  } deriving (Eq, Show)
+
+analyzeFlags :: Parser AnalyzeFlags
+analyzeFlags =
+  AnalyzeFlags <$>
+  textOption
+    (long "astJson" <> value "" <> metavar "PATH" <>
+     help "Path to the ast-json file.") <*>
+  textOption
+    (long "workDir" <> value "work" <> metavar "PATH" <>
+     help "Path to the work directory (for outputs and intermediate files).") <*>
+ switch (long "debug" <> help "Whether to print debug info")
+
+analyzeMain :: IO ()
+analyzeMain = analyze =<< execParser opts
+  where
+    opts =
+      info
+        (analyzeFlags <**> helper)
+        (fullDesc <>
+         progDesc (toS ("Analyze the contract specified at PATH" :: Text)) <>
+         header
+           (toS ("ea-analyze - CLI interface for ethereum-analyzer" :: Text)))
+
+analyze :: AnalyzeFlags -> IO ()
+analyze flags@AnalyzeFlags { astJson = theAstJson
+                           , workDir = theWorkDir
+                           , debug = debug} = do
+  tmpDirname <- getTmpDirname
+  let sessionDir = toS theWorkDir </> toS tmpDirname
+  createDirectoryIfMissing True sessionDir
+  when debug $ putText $ show flags
+  content <-
+    if theAstJson == "" || theAstJson == "-"
+      then getContents
+      else readFile $ toS theAstJson
+  case decodeContracts content of
+    Right contracts -> do
+      savePrettyContracts contracts (toS $
+                                     sessionDir </> "contracts.ir")
+      when debug $ pprintContracts contracts
+      saveCfgs contracts (toS $ sessionDir </> "cfgs")
+      putText "Findings: \n"
+      putText ("\n" `T.intercalate` concatMap findingsFor contracts)
+    Left err -> putText err
+  return ()
+
+getTmpDirname :: IO Text
+getTmpDirname = do
+  t <- getCurrentTime
+  let formated = formatTime defaultTimeLocale (
+        iso8601DateFormat (Just "%H:%M:%S")) t
+  return $ toS formated
+
+savePrettyContracts :: [Contract] -> Text -> IO ()
+savePrettyContracts cs filepath =
+  writeFile (toS filepath) (prettyContracts cs)
+
+saveCfgs :: [Contract] -> Text -> IO ()
+saveCfgs cs dirpath = do
+  createDirectoryIfMissing True (toS dirpath)
+  let hcs = runSimpleUniqueMonad $ mapM hoopleOf cs
+  mapM writeContractCfgs hcs
+  return ()
+  where writeContractCfgs hc = do
+          mapM (writeFunCfgs (toS dirpath </> toS (hcName hc))) (hcFunctions hc)
+          return ()
+        writeFunCfgs contractPrefix hf = do
+          let dot = toDotText (hfCFG hf)
+          writeFile (contractPrefix <.> (toS $ unIdfr $ hfName hf)
+                     <.> "CFG" <.> ".dot") dot
