diff --git a/Makefile b/Makefile
index f47ffa49..f30593ca 100644
--- a/Makefile
+++ b/Makefile
@@ -56,7 +56,7 @@ fmtcheck:
 pretest: lint vet fmtcheck
 
 test: pretest
-	$(foreach pkg,$(PKGS),go test -v $(pkg) || exit;)
+	$(foreach pkg,$(PKGS),go test -cover -v $(pkg) || exit;)
 
 unused :
 	$(foreach pkg,$(PKGS),unused $(pkg);)
diff --git a/README.ja.md b/README.ja.md
index c6599536..7a4069ca 100644
--- a/README.ja.md
+++ b/README.ja.md
@@ -89,6 +89,7 @@ Hello Vulsチュートリアルでは手動でのセットアップ方法で説
 1. 設定
 1. Prepare
 1. Scan
+1. Reporting
 1. TUI(Terminal-Based User Interface)で結果を参照する
 1. Web UI([VulsRepo](https://github.com/usiusi360/vulsrepo))で結果を参照する
 
@@ -125,7 +126,7 @@ Vulsセットアップに必要な以下のソフトウェアをインストー
 - SQLite3 or MySQL
 - git
 - gcc
-- go v1.7.1 or later
+- go v1.7.1 or later (The latest version is recommended)
     - https://golang.org/doc/install
 
 ```bash
@@ -203,6 +204,7 @@ Vulsの設定ファイルを作成する(TOMLフォーマット)
 設定ファイルのチェックを行う
 
 ```
+$ cd $HOME
 $ cat config.toml
 [servers]
 
@@ -224,42 +226,82 @@ $ vuls prepare
 
 ## Step8. Start Scanning
 
-```
-$ vuls scan -cve-dictionary-dbpath=$PWD/cve.sqlite3 -report-json
-INFO[0000] Start scanning (config: /home/ec2-user/config.toml)
-INFO[0000] Start scanning
-INFO[0000] config: /home/ec2-user/config.toml
-INFO[0000] cve-dictionary: /home/ec2-user/cve.sqlite3
 
+```
+$ vuls scan 
+... snip ...
+
+Scan Summary
+============
+172-31-4-82       amazon 2015.09         94 CVEs      103 updatable packages
+
+```
+
+## Step9. Reporting
+
+View one-line summary
+
+```
+$ vuls report -format-one-line-text -cvedb-path=$PWD/cve.sqlite3 
+
+One Line Summary
+================
+172-31-4-82   Total: 94 (High:19 Medium:54 Low:7 ?:14)        103 updatable packages
+
+```
+
+View short summary.
+
+```
+$ vuls report -format-short-text -cvedb-path=$PWD/cve.sqlite3 
+
+172-31-4-8 (amazon 2015.09)
+===========================
+Total: 94 (High:19 Medium:54 Low:7 ?:14)        103 updatable packages
+
+CVE-2016-0705   10.0 (High)     Double free vulnerability in the dsa_priv_decode function in
+                                crypto/dsa/dsa_ameth.c in OpenSSL 1.0.1 before 1.0.1s and 1.0.2 before 1.0.2g
+                                allows remote attackers to cause a denial of service (memory corruption) or
+                                possibly have unspecified other impact via a malformed DSA private key.
+                                http://www.cvedetails.com/cve/CVE-2016-0705
+                                http://people.ubuntu.com/~ubuntu-security/cve/CVE-2016-0705
+                                libssl1.0.0-1.0.2f-2ubuntu1 -> libssl1.0.0-1.0.2g-1ubuntu4.5
+                                openssl-1.0.2f-2ubuntu1 -> openssl-1.0.2g-1ubuntu4.5
 
 ... snip ...
+````
+
+View full report.
+
+```
+$ vuls report -format-full-text -cvedb-path=$PWD/cve.sqlite3 
 
 172-31-4-82 (amazon 2015.09)
 ============================
-CVE-2016-0494   10.0    Unspecified vulnerability in the Java SE and Java SE Embedded components in Oracle
-                        Java SE 6u105, 7u91, and 8u66 and Java SE Embedded 8u65 allows remote attackers to
-                        affect confidentiality, integrity, and availability via unknown vectors related to
-                        2D.
-... snip ...
+Total: 94 (High:19 Medium:54 Low:7 ?:14)        103 updatable packages
 
-CVE-2016-0494
+
+CVE-2016-0705
 -------------
 Score           10.0 (High)
 Vector          (AV:N/AC:L/Au:N/C:C/I:C/A:C)
-Summary         Unspecified vulnerability in the Java SE and Java SE Embedded components in Oracle Java SE 6u105,
-                7u91, and 8u66 and Java SE Embedded 8u65 allows remote attackers to affect confidentiality,
-                integrity, and availability via unknown vectors related to 2D.
-NVD             https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-0494
-MITRE           https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-0494
-CVE Details     http://www.cvedetails.com/cve/CVE-2016-0494
-CVSS Calculator https://nvd.nist.gov/cvss/v2-calculator?name=CVE-2016-0494&vector=(AV:N/AC:L/Au:N/C:C/I:C/A:C)
-RHEL-CVE        https://access.redhat.com/security/cve/CVE-2016-0494
-ALAS-2016-643   https://alas.aws.amazon.com/ALAS-2016-643.html
-Package/CPE     java-1.7.0-openjdk-1.7.0.91-2.6.2.2.63.amzn1 -> java-1.7.0-openjdk-1:1.7.0.95-2.6.4.0.65.amzn1
+Summary         Double free vulnerability in the dsa_priv_decode function in
+                crypto/dsa/dsa_ameth.c in OpenSSL 1.0.1 before 1.0.1s and 1.0.2 before 1.0.2g
+                allows remote attackers to cause a denial of service (memory corruption) or
+                possibly have unspecified other impact via a malformed DSA private key.
+CWE             https://cwe.mitre.org/data/definitions/.html
+NVD             https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-0705
+MITRE           https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-0705
+CVE Details     http://www.cvedetails.com/cve/CVE-2016-0705
+CVSS Claculator https://nvd.nist.gov/cvss/v2-calculator?name=CVE-2016-0705&vector=(AV:N/AC:L/...
+Ubuntu-CVE      http://people.ubuntu.com/~ubuntu-security/cve/CVE-2016-0705
+Package         libssl1.0.0-1.0.2f-2ubuntu1 -> libssl1.0.0-1.0.2g-1ubuntu4.5
+                openssl-1.0.2f-2ubuntu1 -> openssl-1.0.2g-1ubuntu4.5
 
+... snip ...
 ```
 
-## Step9. TUI
+## Step10. TUI
 
 Vulsにはスキャン結果の詳細を参照できるイカしたTUI(Terminal-Based User Interface)が付属している。
 
@@ -269,7 +311,7 @@ $ vuls tui
 
 
 
-## Step10. Web UI
+## Step11. Web UI
 
 [VulsRepo](https://github.com/usiusi360/vulsrepo)はスキャン結果をビボットテーブルのように分析可能にするWeb UIである。  
 [Online Demo](http://usiusi360.github.io/vulsrepo/)があるので試してみて。
@@ -369,7 +411,7 @@ iconEmoji    = ":ghost:"
 authUser     = "username"
 notifyUsers  = ["@username"]
 
-[mail]
+[email]
 smtpAddr      = "smtp.gmail.com"
 smtpPort      = "587"
 user          = "username"
@@ -450,7 +492,7 @@ host         = "172.31.4.82"
 
 - Mail section
     ```
-    [mail]
+    [email]
     smtpAddr      = "smtp.gmail.com"
     smtpPort      = "587"
     user          = "username"
@@ -571,7 +613,7 @@ Prepareサブコマンドは、Vuls内部で利用する以下のパッケージ
 | CentOS      |                   5| yum-changelog |
 | CentOS      |                6, 7| yum-plugin-changelog |
 | Amazon      |                All | -            |
-| RHEL        |         4, 5, 6, 7 | -            |
+| RHEL        |               6, 7 | -            |
 | FreeBSD     |                 10 | -            |
 
 
@@ -603,90 +645,31 @@ prepare:
 $ vuls scan -help
 scan:
         scan
-                [-lang=en|ja]
                 [-config=/path/to/config.toml]
                 [-results-dir=/path/to/results]
-                [-cve-dictionary-dbtype=sqlite3|mysql]
-                [-cve-dictionary-dbpath=/path/to/cve.sqlite3 or mysql connection string]
-                [-cve-dictionary-url=http://127.0.0.1:1323]
-                [-cache-dbpath=/path/to/cache.db]
-                [-cvss-over=7]
-                [-ignore-unscored-cves]
+                [-cachedb-path=/path/to/cache.db]
                 [-ssh-external]
                 [-containers-only]
                 [-skip-broken]
-                [-report-azure-blob]
-                [-report-json]
-                [-report-mail]
-                [-report-s3]
-                [-report-slack]
-                [-report-text]
-                [-report-xml]
                 [-http-proxy=http://192.168.0.1:8080]
                 [-ask-key-password]
                 [-debug]
-                [-debug-sql]
-                [-aws-profile=default]
-                [-aws-region=us-west-2]
-                [-aws-s3-bucket=bucket_name]
-                [-azure-account=accout]
-                [-azure-key=key]
-                [-azure-container=container]
+
                 [SERVER]...
-
-
   -ask-key-password
         Ask ssh privatekey password before scanning
-  -aws-profile string
-        AWS Profile to use (default "default")
-  -aws-region string
-        AWS Region to use (default "us-east-1")
-  -aws-s3-bucket string
-        S3 bucket name
-  -azure-account string
-        Azure account name to use. AZURE_STORAGE_ACCOUNT environment variable is used if not specified
-  -azure-container string
-        Azure storage container name
-  -azure-key string
-        Azure account key to use. AZURE_STORAGE_ACCESS_KEY environment variable is used if not specified
-  -cache-dbpath string
-        /path/to/cache.db (local cache of changelog for Ubuntu/Debian) (default "$PWD/cache.db")
+  -cachedb-path string
+        /path/to/cache.db (local cache of changelog for Ubuntu/Debian)
   -config string
-        /path/to/toml (default "$PWD/config.toml")
+        /path/to/toml 
   -containers-only
-        Scan concontainers Only. Default: Scan both of hosts and containers
-  -cve-dictionary-dbpath string
-        /path/to/sqlite3 (For get cve detail from cve.sqlite3)
-  -cve-dictionary-dbtype string
-        DB type for fetching CVE dictionary (sqlite3 or mysql) (default "sqlite3")
-  -cve-dictionary-url string
-        http://CVE.Dictionary (default "http://127.0.0.1:1323")
-  -cvss-over float
-        -cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))
+        Scan containers only. Default: Scan both of hosts and containers
   -debug
         debug mode
-  -debug-sql
-        SQL debug mode
   -http-proxy string
         http://proxy-url:port (default: empty)
-  -ignore-unscored-cves
-        Don't report the unscored CVEs
-  -lang string
-        [en|ja] (default "en")
-  -report-json
-        Write report to JSON files ($PWD/results/current)
-  -report-mail
-        Send report via Email
-  -report-s3
-        Write report to S3 (bucket/yyyyMMdd_HHmm)
-  -report-slack
-        Send report via Slack
-  -report-text
-        Write report to text files ($PWD/results/current)
-  -report-xml
-        Write report to XML files ($PWDresults/current)
   -results-dir string
-        /path/to/results (default "$PWD/results")
+        /path/to/results 
   -skip-broken
         [For CentOS] yum update changelog with --skip-broken option
   -ssh-external
@@ -714,38 +697,167 @@ Defaults:vuls !requiretty
 | empty password   |                 -  | |
 | with password    |           required | or use ssh-agent |
 
-## -report-json , -report-text , -report-xml option
-
-結果をファイルに出力したい場合に指定する。出力先は、`$PWD/result/current/`    
-`servername.(json|txt|xml)`には、サーバごとのスキャン結果が出力される。
-
 ## Example: Scan all servers defined in config file
 ```
-$ vuls scan \
-      -report-slack \ 
-      -report-mail \
-      -cvss-over=7 \
-      -ask-key-password \
-      -cve-dictionary-dbpath=$PWD/cve.sqlite3
+$ vuls scan -ask-key-password 
 ```
 この例では、
 - SSH公開鍵認証(秘密鍵パスフレーズ)を指定
 - configに定義された全サーバをスキャン
-- レポートをslack, emailに送信
-- CVSSスコアが 7.0 以上の脆弱性のみレポート
-- go-cve-dictionaryにはHTTPではなくDBに直接アクセス(go-cve-dictionaryをサーバモードで起動しない)
 
 ## Example: Scan specific servers
 ```
-$ vuls scan \
-      -cve-dictionary-dbpath=$PWD/cve.sqlite3 \ 
-      server1 server2
+$ vuls scan server1 server2
 ```
 この例では、
 - SSH公開鍵認証(秘密鍵パスフレーズなし)
 - ノーパスワードでsudoが実行可能
 - configで定義されているサーバの中の、server1, server2のみスキャン
 
+## Example: Scan Docker containers
+
+DockerコンテナはSSHデーモンを起動しないで運用するケースが一般的。  
+ [Docker Blog:Why you don't need to run SSHd in your Docker containers](https://blog.docker.com/2014/06/why-you-dont-need-to-run-sshd-in-docker/)
+
+Vulsは、DockerホストにSSHで接続し、`docker exec`でDockerコンテナにコマンドを発行して脆弱性をスキャンする。  
+詳細は、[Architecture section](https://github.com/future-architect/vuls#architecture)を参照
+
+- 全ての起動中のDockerコンテナをスキャン  
+  `"${running}"` をcontainersに指定する
+    ```
+    [servers]
+
+    [servers.172-31-4-82]
+    host         = "172.31.4.82"
+    user        = "ec2-user"
+    keyPath     = "/home/username/.ssh/id_rsa"
+    containers = ["${running}"]
+    ```
+
+- あるコンテナのみスキャン  
+  コンテナID、または、コンテナ名を、containersに指定する。  
+  以下の例では、`container_name_a`と、`4aa37a8b63b9`のコンテナのみスキャンする  
+  スキャン実行前に、コンテナが起動中か確認すること。もし起動してない場合はエラーメッセージを出力してスキャンを中断する。  
+    ```
+    [servers]
+
+    [servers.172-31-4-82]
+    host         = "172.31.4.82"
+    user        = "ec2-user"
+    keyPath     = "/home/username/.ssh/id_rsa"
+    containers = ["container_name_a", "4aa37a8b63b9"]
+    ```
+- コンテナのみをスキャンする場合(ホストはスキャンしない)  
+  --containers-onlyオプションを指定する
+
+
+# Usage: Report
+
+```
+report:
+        report
+                [-lang=en|ja]
+                [-config=/path/to/config.toml]
+                [-results-dir=/path/to/results]
+                [-refresh-cve]
+                [-cvedb-type=sqlite3|mysql]
+                [-cvedb-path=/path/to/cve.sqlite3]
+                [-cvedb-url=http://127.0.0.1:1323 or mysql connection string]
+                [-cvss-over=7]
+                [-ignore-unscored-cves]
+                [-to-email]
+                [-to-slack]
+                [-to-localfile]
+                [-to-s3]
+                [-to-azure-blob]
+                [-format-json]
+                [-format-xml]
+                [-format-one-line-text]
+                [-format-short-text]
+                [-format-full-text]
+                [-gzip]
+                [-aws-profile=default]
+                [-aws-region=us-west-2]
+                [-aws-s3-bucket=bucket_name]
+                [-azure-account=accout]
+                [-azure-key=key]
+                [-azure-container=container]
+                [-http-proxy=http://192.168.0.1:8080]
+                [-debug]
+                [-debug-sql]
+
+                [SERVER]...
+  -aws-profile string
+        AWS profile to use (default "default")
+  -aws-region string
+        AWS region to use (default "us-east-1")
+  -aws-s3-bucket string
+        S3 bucket name
+  -azure-account string
+        Azure account name to use. AZURE_STORAGE_ACCOUNT environment variable is used if not specified
+  -azure-container string
+        Azure storage container name
+  -azure-key string
+        Azure account key to use. AZURE_STORAGE_ACCESS_KEY environment variable is used if not specified
+  -config string
+        /path/to/toml 
+  -cvedb-path string
+        /path/to/sqlite3 (For get cve detail from cve.sqlite3)
+  -cvedb-type string
+        DB type for fetching CVE dictionary (sqlite3 or mysql) (default "sqlite3")
+  -cvedb-url string
+        http://cve-dictionary.com:8080 or mysql connection string
+  -cvss-over float
+        -cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))
+  -debug
+        debug mode
+  -debug-sql
+        SQL debug mode
+  -format-full-text
+        Detail report in plain text
+  -format-json
+        JSON format
+  -format-one-line-text
+        One line summary in plain text
+  -format-short-text
+        Summary in plain text
+  -format-xml
+        XML format
+  -gzip
+        gzip compression
+  -http-proxy string
+        http://proxy-url:port (default: empty)
+  -ignore-unscored-cves
+        Don't report the unscored CVEs
+  -lang string
+        [en|ja] (default "en")
+  -refresh-cve
+        Refresh CVE information in JSON file under results dir
+  -results-dir string
+        /path/to/results 
+  -to-azure-blob
+        Write report to Azure Storage blob (container/yyyyMMdd_HHmm/servername.json/xml/txt)
+  -to-email
+        Send report via Email
+  -to-localfile
+        Write report to localfile
+  -to-s3
+        Write report to S3 (bucket/yyyyMMdd_HHmm/servername.json/xml/txt)
+  -to-slack
+        Send report via Slack
+```
+
+## Example: Send scan results to Slack
+```
+$ vuls report \
+      -to-slack \
+      -cvss-over=7 \
+      -cvedb-path=$PWD/cve.sqlite3
+```
+With this sample command, it will ..
+- Slack通知
+- CVSS score が 7.0以上のもののみ通知
+
 ## Example: Put results in S3 bucket
 
 事前にAWS関連の設定を行う
@@ -755,15 +867,14 @@ $ vuls scan \
 
 ```
 $ vuls scan \
-      -cve-dictionary-dbpath=$PWD/cve.sqlite3 \ 
-      -report-s3
+      -cvedb-path=$PWD/cve.sqlite3 \ 
+      -to-s3 \
+      -format-json \
       -aws-region=ap-northeast-1 \
       -aws-s3-bucket=vuls \
       -aws-profile=default 
 ```
 この例では、
-- SSH公開鍵認証(秘密鍵パスフレーズなし)
-- configに定義された全サーバをスキャン
 - 結果をJSON形式でS3に格納する。
   - バケット名 ... vuls
   - リージョン ... ap-northeast-1
@@ -772,20 +883,19 @@ $ vuls scan \
 ## Example: Put results in Azure Blob storage
 
 事前にAzure Blob関連の設定を行う
-- Containerを作成
+- Azure Blob Containerを作成
 
 ```
 $ vuls scan \
-      -cve-dictionary-dbpath=$PWD/cve.sqlite3 \ 
-      -report-azure-blob \
+      -cvedb-path=$PWD/cve.sqlite3 \ 
+      -to-azure-blob \
+      -format-xml \
       -azure-container=vuls \
       -azure-account=test \
       -azure-key=access-key-string 
 ```
 この例では、
-- SSH公開鍵認証(秘密鍵パスフレーズなし)
-- configに定義された全サーバをスキャン
-- 結果をJSON形式でAzure Blobに格納する。
+- 結果をXML形式でBlobに格納する。
   - コンテナ名 ... vuls
   - ストレージアカウント名 ... test
   - アクセスキー ... access-key-string
@@ -802,7 +912,7 @@ $ vuls scan \
 
 ## Example: IgnoreCves 
 
-Slack, Mail, テキスト出力しないくないCVE IDがある場合は、設定ファイルに定義することでレポートされなくなる。
+Slack, EMail, テキスト出力しないくないCVE IDがある場合は、設定ファイルに定義することでレポートされなくなる。
 ただ、JSONファイルには以下のように出力される。
 
 - config.toml
@@ -938,43 +1048,6 @@ VulsとDependency Checkの連携すると以下の利点がある
   - Dependency Checkは日本語レポートに対応していない
 
     
-# Usage: Scan Docker containers
-
-DockerコンテナはSSHデーモンを起動しないで運用するケースが一般的。  
- [Docker Blog:Why you don't need to run SSHd in your Docker containers](https://blog.docker.com/2014/06/why-you-dont-need-to-run-sshd-in-docker/)
-
-Vulsは、DockerホストにSSHで接続し、`docker exec`でDockerコンテナにコマンドを発行して脆弱性をスキャンする。  
-詳細は、[Architecture section](https://github.com/future-architect/vuls#architecture)を参照
-
-- 全ての起動中のDockerコンテナをスキャン  
-  `"${running}"` をcontainersに指定する
-    ```
-    [servers]
-
-    [servers.172-31-4-82]
-    host         = "172.31.4.82"
-    user        = "ec2-user"
-    keyPath     = "/home/username/.ssh/id_rsa"
-    containers = ["${running}"]
-    ```
-
-- あるコンテナのみスキャン  
-  コンテナID、または、コンテナ名を、containersに指定する。  
-  以下の例では、`container_name_a`と、`4aa37a8b63b9`のコンテナのみスキャンする  
-  スキャン実行前に、コンテナが起動中か確認すること。もし起動してない場合はエラーメッセージを出力してスキャンを中断する。  
-    ```
-    [servers]
-
-    [servers.172-31-4-82]
-    host         = "172.31.4.82"
-    user        = "ec2-user"
-    keyPath     = "/home/username/.ssh/id_rsa"
-    containers = ["container_name_a", "4aa37a8b63b9"]
-    ```
-- コンテナのみをスキャンする場合(ホストはスキャンしない)  
-  --containers-onlyオプションを指定する
-
-
 # Usage: TUI
 
 ## Display the latest scan results
@@ -982,13 +1055,26 @@ Vulsは、DockerホストにSSHで接続し、`docker exec`でDockerコンテナ
 ```
 $ vuls tui -h
 tui:
-	tui [-results-dir=/path/to/results]
+        tui
+                [-cvedb-type=sqlite3|mysql]
+                [-cvedb-path=/path/to/cve.sqlite3]
+                [-cvedb-url=http://127.0.0.1:1323 or mysql connection string]
+                [-results-dir=/path/to/results]
+                [-refresh-cve]
+                [-debug-sql]
 
-  -results-dir string
-        /path/to/results (default "$PWD/results")
+  -cvedb-path string
+        /path/to/sqlite3 (For get cve detail from cve.sqlite3)
+  -cvedb-type string
+        DB type for fetching CVE dictionary (sqlite3 or mysql)
+  -cvedb-url string
+        http://cve-dictionary.com:8080 or mysql connection string
   -debug-sql
-    	debug SQL
-
+        debug SQL
+  -refresh-cve
+        Refresh CVE information in JSON file under results dir
+  -results-dir string
+        /path/to/results 
 ```
 
 Key binding is below.
diff --git a/README.md b/README.md
index 0a28a6e6..48715b4b 100644
--- a/README.md
+++ b/README.md
@@ -57,7 +57,7 @@ Vuls is a tool created to solve the problems listed above. It has the following
 - Auto generation of configuration file template
     - Auto detection of servers set using CIDR, generate configuration file template
 - Email and Slack notification is possible (supports Japanese language)
-- Scan result is viewable on accessory software, TUI Viewer terminal or Web UI ([VulsRepo](https://github.com/usiusi360/vulsrepo)).
+- Scan result is viewable on accessory software, TUI Viewer on terminal or Web UI ([VulsRepo](https://github.com/usiusi360/vulsrepo)).
 
 ----
 
@@ -69,16 +69,13 @@ Vuls is a tool created to solve the problems listed above. It has the following
 
 # Setup Vuls
 
-There are 3 ways to setup Vuls.
+There are 2 ways to setup Vuls.
 
 - Docker container  
 Dockernized-Vuls with vulsrepo UI in it.  
 You can run install and run Vuls on your machine with only a few commands.  
 see https://github.com/future-architect/vuls/tree/master/setup/docker
 
-- Chef  
-see https://github.com/sadayuki-matsuno/vuls-cookbook
-
 - Manually  
 Hello Vuls Tutorial shows how to setup vuls manually.
 
@@ -97,6 +94,7 @@ This can be done in the following steps.
 1. Configuration
 1. Prepare
 1. Scan
+1. Reporting
 1. TUI(Terminal-Based User Interface)
 1. Web UI ([VulsRepo](https://github.com/usiusi360/vulsrepo))
 
@@ -133,7 +131,7 @@ Vuls requires the following packages.
 - SQLite3 or MySQL
 - git
 - gcc
-- go v1.7.1 or later
+- go v1.7.1 or later (The latest version is recommended)
     - https://golang.org/doc/install
 
 ```bash
@@ -200,6 +198,7 @@ Create a config file(TOML format).
 Then check the config.
 
 ```
+$ cd $HOME
 $ cat config.toml
 [servers]
 
@@ -222,51 +221,90 @@ see [Usage: Prepare](https://github.com/future-architect/vuls#usage-prepare)
 ## Step8. Start Scanning
 
 ```
-$ vuls scan -cve-dictionary-dbpath=$PWD/cve.sqlite3 -report-json
-INFO[0000] Start scanning (config: /home/ec2-user/config.toml)
-INFO[0000] Start scanning
-INFO[0000] config: /home/ec2-user/config.toml
-INFO[0000] cve-dictionary: /home/ec2-user/cve.sqlite3
-
-
+$ vuls scan 
 ... snip ...
 
-172-31-4-82 (amazon 2015.09)
-============================
-CVE-2016-0494   10.0    Unspecified vulnerability in the Java SE and Java SE Embedded components in Oracle
-                        Java SE 6u105, 7u91, and 8u66 and Java SE Embedded 8u65 allows remote attackers to
-                        affect confidentiality, integrity, and availability via unknown vectors related to
-                        2D.
-... snip ...
-
-CVE-2016-0494
--------------
-Score           10.0 (High)
-Vector          (AV:N/AC:L/Au:N/C:C/I:C/A:C)
-Summary         Unspecified vulnerability in the Java SE and Java SE Embedded components in Oracle Java SE 6u105,
-                7u91, and 8u66 and Java SE Embedded 8u65 allows remote attackers to affect confidentiality,
-                integrity, and availability via unknown vectors related to 2D.
-NVD             https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-0494
-MITRE           https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-0494
-CVE Details     http://www.cvedetails.com/cve/CVE-2016-0494
-CVSS Calculator https://nvd.nist.gov/cvss/v2-calculator?name=CVE-2016-0494&vector=(AV:N/AC:L/Au:N/C:C/I:C/A:C)
-RHEL-CVE        https://access.redhat.com/security/cve/CVE-2016-0494
-ALAS-2016-643   https://alas.aws.amazon.com/ALAS-2016-643.html
-Package/CPE     java-1.7.0-openjdk-1.7.0.91-2.6.2.2.63.amzn1 -> java-1.7.0-openjdk-1:1.7.0.95-2.6.4.0.65.amzn1
+Scan Summary
+============
+172-31-4-82       amazon 2015.09         94 CVEs      103 updatable packages
 
 ```
 
-## Step9. TUI
+## Step9. Reporting
+
+View one-line summary
+
+```
+$ vuls report -format-one-line-text -cvedb-path=$PWD/cve.sqlite3 
+
+One Line Summary
+================
+172-31-4-82   Total: 94 (High:19 Medium:54 Low:7 ?:14)        103 updatable packages
+
+```
+
+View short summary.
+
+```
+$ vuls report -format-short-text 
+
+172-31-4-8 (amazon 2015.09)
+===========================
+Total: 94 (High:19 Medium:54 Low:7 ?:14)        103 updatable packages
+
+CVE-2016-0705   10.0 (High)     Double free vulnerability in the dsa_priv_decode function in
+                                crypto/dsa/dsa_ameth.c in OpenSSL 1.0.1 before 1.0.1s and 1.0.2 before 1.0.2g
+                                allows remote attackers to cause a denial of service (memory corruption) or
+                                possibly have unspecified other impact via a malformed DSA private key.
+                                http://www.cvedetails.com/cve/CVE-2016-0705
+                                http://people.ubuntu.com/~ubuntu-security/cve/CVE-2016-0705
+                                libssl1.0.0-1.0.2f-2ubuntu1 -> libssl1.0.0-1.0.2g-1ubuntu4.5
+                                openssl-1.0.2f-2ubuntu1 -> openssl-1.0.2g-1ubuntu4.5
+
+... snip ...
+````
+
+View full report.
+
+```
+$ vuls report -format-full-text 
+
+172-31-4-82 (amazon 2015.09)
+============================
+Total: 94 (High:19 Medium:54 Low:7 ?:14)        103 updatable packages
+
+
+CVE-2016-0705
+-------------
+Score           10.0 (High)
+Vector          (AV:N/AC:L/Au:N/C:C/I:C/A:C)
+Summary         Double free vulnerability in the dsa_priv_decode function in
+                crypto/dsa/dsa_ameth.c in OpenSSL 1.0.1 before 1.0.1s and 1.0.2 before 1.0.2g
+                allows remote attackers to cause a denial of service (memory corruption) or
+                possibly have unspecified other impact via a malformed DSA private key.
+CWE             https://cwe.mitre.org/data/definitions/.html
+NVD             https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-0705
+MITRE           https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-0705
+CVE Details     http://www.cvedetails.com/cve/CVE-2016-0705
+CVSS Claculator https://nvd.nist.gov/cvss/v2-calculator?name=CVE-2016-0705&vector=(AV:N/AC:L/...
+Ubuntu-CVE      http://people.ubuntu.com/~ubuntu-security/cve/CVE-2016-0705
+Package         libssl1.0.0-1.0.2f-2ubuntu1 -> libssl1.0.0-1.0.2g-1ubuntu4.5
+                openssl-1.0.2f-2ubuntu1 -> openssl-1.0.2g-1ubuntu4.5
+
+... snip ...
+```
+
+## Step10. TUI
 
 Vuls has Terminal-Based User Interface to display the scan result.
 
 ```
-$ vuls tui
+$ vuls tui 
 ```
 
 
 
-## Step10. Web UI
+## Step11. Web UI
 
 [VulsRepo](https://github.com/usiusi360/vulsrepo) is a awesome Web UI for Vuls.  
 Check it out the [Online Demo](http://usiusi360.github.io/vulsrepo/).
@@ -288,11 +326,8 @@ see https://github.com/future-architect/vuls/tree/master/setup/docker
 
 ## Scanning Flow
 
-- Scan vulnerabilities on the servers via SSH and create a list of the CVE ID
+- Scan vulnerabilities on the servers via SSH and collect a list of the CVE ID
   - To scan Docker containers, Vuls connect via ssh to the Docker host and then `docker exec` to the containers. So, no need to run sshd daemon on the containers.
-- Fetch more detailed information of the detected CVE from go-cve-dictionary
-- Send a report by Slack and Email
-- Write scan results to JSON file to show the latest report on your terminal
 
 ----
 # Performance Considerations
@@ -323,16 +358,20 @@ High speed scan and resource usage is light because Vuls can get CVE IDs by usin
 
 # Use Cases
 
-## Scan all servers
+## Scan All Servers
 
 
 
-## Scan a single server
+## Scan a Single Server
 
 web/app server in the same configuration under the load balancer
 
 
 
+## Scan Staging Environment
+
+If there is a staging environment with the same configuration as the production environment, you can scan the server in staging environment
+
 ----
 
 # Support OS
@@ -373,7 +412,7 @@ iconEmoji    = ":ghost:"
 authUser     = "username"
 notifyUsers  = ["@username"]
 
-[mail]
+[email]
 smtpAddr      = "smtp.gmail.com"
 smtpPort      = "587"
 user          = "username"
@@ -457,9 +496,9 @@ You can customize your configuration using this template.
       If you set `["@foo", "@bar"]` to notifyUsers, @foo @bar will be included in text.  
       So @foo, @bar can receive mobile push notifications on their smartphone.  
 
-- Mail section
+- EMail section
     ```
-    [mail]
+    [email]
     smtpAddr      = "smtp.gmail.com"
     smtpPort      = "587"
     user          = "username"
@@ -577,7 +616,7 @@ Prepare subcommand installs required packages on each server.
 | CentOS      |                   5| yum-changelog |
 | CentOS      |                6, 7| yum-plugin-changelog |
 | Amazon      |                All | -            |
-| RHEL        |         4, 5, 6, 7 | -            |
+| RHEL        |               6, 7 | -            |
 | FreeBSD     |                 10 | -            |
 
 
@@ -610,94 +649,34 @@ prepare:
 # Usage: Scan
 
 ```
-
 $ vuls scan -help
 scan:
         scan
-                [-lang=en|ja]
                 [-config=/path/to/config.toml]
                 [-results-dir=/path/to/results]
-                [-cve-dictionary-dbtype=sqlite3|mysql]
-                [-cve-dictionary-dbpath=/path/to/cve.sqlite3 or mysql connection string]
-                [-cve-dictionary-url=http://127.0.0.1:1323]
-                [-cache-dbpath=/path/to/cache.db]
-                [-cvss-over=7]
-                [-ignore-unscored-cves]
+                [-cachedb-path=/path/to/cache.db]
                 [-ssh-external]
                 [-containers-only]
                 [-skip-broken]
-                [-report-azure-blob]
-                [-report-json]
-                [-report-mail]
-                [-report-s3]
-                [-report-slack]
-                [-report-text]
-                [-report-xml]
                 [-http-proxy=http://192.168.0.1:8080]
                 [-ask-key-password]
                 [-debug]
-                [-debug-sql]
-                [-aws-profile=default]
-                [-aws-region=us-west-2]
-                [-aws-s3-bucket=bucket_name]
-                [-azure-account=accout]
-                [-azure-key=key]
-                [-azure-container=container]
+
                 [SERVER]...
-
-
   -ask-key-password
         Ask ssh privatekey password before scanning
-  -aws-profile string
-        AWS Profile to use (default "default")
-  -aws-region string
-        AWS Region to use (default "us-east-1")
-  -aws-s3-bucket string
-        S3 bucket name
-  -azure-account string
-        Azure account name to use. AZURE_STORAGE_ACCOUNT environment variable is used if not specified
-  -azure-container string
-        Azure storage container name
-  -azure-key string
-        Azure account key to use. AZURE_STORAGE_ACCESS_KEY environment variable is used if not specified
-  -cache-dbpath string
-        /path/to/cache.db (local cache of changelog for Ubuntu/Debian) (default "$PWD/cache.db")
+  -cachedb-path string
+        /path/to/cache.db (local cache of changelog for Ubuntu/Debian)
   -config string
-        /path/to/toml (default "$PWD/config.toml")
+        /path/to/toml 
   -containers-only
-        Scan concontainers Only. Default: Scan both of hosts and containers
-  -cve-dictionary-dbpath string
-        /path/to/sqlite3 (For get cve detail from cve.sqlite3)
-  -cve-dictionary-dbtype string
-        DB type for fetching CVE dictionary (sqlite3 or mysql) (default "sqlite3")
-  -cve-dictionary-url string
-        http://CVE.Dictionary (default "http://127.0.0.1:1323")
-  -cvss-over float
-        -cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))
+        Scan containers only. Default: Scan both of hosts and containers
   -debug
         debug mode
-  -debug-sql
-        SQL debug mode
   -http-proxy string
         http://proxy-url:port (default: empty)
-  -ignore-unscored-cves
-        Don't report the unscored CVEs
-  -lang string
-        [en|ja] (default "en")
-  -report-json
-        Write report to JSON files ($PWD/results/current)
-  -report-mail
-        Send report via Email
-  -report-s3
-        Write report to S3 (bucket/yyyyMMdd_HHmm)
-  -report-slack
-        Send report via Slack
-  -report-text
-        Write report to text files ($PWD/results/current)
-  -report-xml
-        Write report to XML files ($PWDresults/current)
   -results-dir string
-        /path/to/results (default "$PWD/results")
+        /path/to/results 
   -skip-broken
         [For CentOS] yum update changelog with --skip-broken option
   -ssh-external
@@ -726,73 +705,200 @@ Defaults:vuls !requiretty
 | empty password   |                 -  | |
 | with password    |           required | or use ssh-agent |
 
-## -report-json , -report-text , -report-xml option
-
-At the end of the scan, scan results will be available in the `$PWD/result/current/` directory.  
-`servername.(json|txt|xml)` includes the scan result of the server.
-
 ## Example: Scan all servers defined in config file
 ```
-$ vuls scan \
-      --report-slack \
-      --report-mail \
-      --cvss-over=7 \
-      -ask-key-password \
-      -cve-dictionary-dbpath=$PWD/cve.sqlite3
+$ vuls scan -ask-key-password
 ```
 With this sample command, it will ..
 - Ask SSH key password before scanning
 - Scan all servers defined in config file
-- Send scan results to slack and email
-- Only Report CVEs that CVSS score is over 7
-- Print scan result to terminal
 
 ## Example: Scan specific servers
 ```
-$ vuls scan \
-      -cve-dictionary-dbpath=$PWD/cve.sqlite3 \
-      server1 server2
+$ vuls scan server1 server2
 ```
 With this sample command, it will ..
 - Use SSH Key-Based authentication with empty password (without -ask-key-password option)
 - Scan only 2 servers (server1, server2)
-- Print scan result to terminal
+
+## Example: Scan Docker containers
+
+It is common that keep Docker containers running without SSHd daemon.  
+see [Docker Blog:Why you don't need to run SSHd in your Docker containers](https://blog.docker.com/2014/06/why-you-dont-need-to-run-sshd-in-docker/)
+
+Vuls scans Docker containers via `docker exec` instead of SSH.  
+For more details, see [Architecture section](https://github.com/future-architect/vuls#architecture)
+
+- To scan all of running containers  
+  `"${running}"` needs to be set in the containers item.
+    ```
+    [servers]
+
+    [servers.172-31-4-82]
+    host         = "172.31.4.82"
+    user        = "ec2-user"
+    keyPath     = "/home/username/.ssh/id_rsa"
+    containers = ["${running}"]
+    ```
+
+- To scan specific containers  
+  The container ID or container name needs to be set in the containers item.  
+  In the following example, only `container_name_a` and `4aa37a8b63b9` will be scanned.  
+  Be sure to check these containers are running state before scanning.  
+  If specified containers are not running, Vuls gives up scanning with printing error message.
+    ```
+    [servers]
+
+    [servers.172-31-4-82]
+    host         = "172.31.4.82"
+    user        = "ec2-user"
+    keyPath     = "/home/username/.ssh/id_rsa"
+    containers = ["container_name_a", "4aa37a8b63b9"]
+    ```
+- To scan containers only
+  - --containers-only option is available.
+
+----
+
+# Usage: Report
+
+```
+report:
+        report
+                [-lang=en|ja]
+                [-config=/path/to/config.toml]
+                [-results-dir=/path/to/results]
+                [-refresh-cve]
+                [-cvedb-type=sqlite3|mysql]
+                [-cvedb-path=/path/to/cve.sqlite3]
+                [-cvedb-url=http://127.0.0.1:1323 or mysql connection string]
+                [-cvss-over=7]
+                [-ignore-unscored-cves]
+                [-to-email]
+                [-to-slack]
+                [-to-localfile]
+                [-to-s3]
+                [-to-azure-blob]
+                [-format-json]
+                [-format-xml]
+                [-format-one-line-text]
+                [-format-short-text]
+                [-format-full-text]
+                [-gzip]
+                [-aws-profile=default]
+                [-aws-region=us-west-2]
+                [-aws-s3-bucket=bucket_name]
+                [-azure-account=accout]
+                [-azure-key=key]
+                [-azure-container=container]
+                [-http-proxy=http://192.168.0.1:8080]
+                [-debug]
+                [-debug-sql]
+
+                [SERVER]...
+  -aws-profile string
+        AWS profile to use (default "default")
+  -aws-region string
+        AWS region to use (default "us-east-1")
+  -aws-s3-bucket string
+        S3 bucket name
+  -azure-account string
+        Azure account name to use. AZURE_STORAGE_ACCOUNT environment variable is used if not specified
+  -azure-container string
+        Azure storage container name
+  -azure-key string
+        Azure account key to use. AZURE_STORAGE_ACCESS_KEY environment variable is used if not specified
+  -config string
+        /path/to/toml 
+  -cvedb-path string
+        /path/to/sqlite3 (For get cve detail from cve.sqlite3)
+  -cvedb-type string
+        DB type for fetching CVE dictionary (sqlite3 or mysql) (default "sqlite3")
+  -cvedb-url string
+        http://cve-dictionary.com:8080 or mysql connection string
+  -cvss-over float
+        -cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))
+  -debug
+        debug mode
+  -debug-sql
+        SQL debug mode
+  -format-full-text
+        Detail report in plain text
+  -format-json
+        JSON format
+  -format-one-line-text
+        One line summary in plain text
+  -format-short-text
+        Summary in plain text
+  -format-xml
+        XML format
+  -gzip
+        gzip compression
+  -http-proxy string
+        http://proxy-url:port (default: empty)
+  -ignore-unscored-cves
+        Don't report the unscored CVEs
+  -lang string
+        [en|ja] (default "en")
+  -refresh-cve
+        Refresh CVE information in JSON file under results dir
+  -results-dir string
+        /path/to/results 
+  -to-azure-blob
+        Write report to Azure Storage blob (container/yyyyMMdd_HHmm/servername.json/xml/txt)
+  -to-email
+        Send report via Email
+  -to-localfile
+        Write report to localfile
+  -to-s3
+        Write report to S3 (bucket/yyyyMMdd_HHmm/servername.json/xml/txt)
+  -to-slack
+        Send report via Slack
+```
+
+## Example: Send scan results to Slack
+```
+$ vuls report \
+      -to-slack \
+      -cvss-over=7 \
+      -cvedb-path=$PWD/cve.sqlite3
+```
+With this sample command, it will ..
+- Send scan results to slack
+- Only Report CVEs that CVSS score is over 7
 
 ## Example: Put results in S3 bucket
-To put results in S3 bucket, configure following settings in AWS before scanning.
+To put results in S3 bucket, configure following settings in AWS before reporting.
 - Create S3 bucket. see [Creating a Bucket](http://docs.aws.amazon.com/AmazonS3/latest/UG/CreatingaBucket.html)  
 - Create access key. The access key must have read and write access to the AWS S3 bucket. see [Managing Access Keys for IAM Users](http://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html)
 - Configure the security credentials. see [Configuring the AWS Command Line Interface](http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html)
 
 ```
-$ vuls scan \
-      -cve-dictionary-dbpath=$PWD/cve.sqlite3 \
-      -report-s3 \
+$ vuls report \
+      -cvedb-path=$PWD/cve.sqlite3 \
+      -to-s3 \
+      -format-json \
       -aws-region=ap-northeast-1 \
       -aws-s3-bucket=vuls \
       -aws-profile=default
 ```
 With this sample command, it will ..
-- Use SSH Key-Based authentication with empty password (without -ask-key-password option)
-- Scan all servers defined in config file
 - Put scan result(JSON) in S3 bucket. The bucket name is "vuls" in ap-northeast-1 and profile is "default"
 
 ## Example: Put results in Azure Blob storage
 
-To put results in Azure Blob Storage, configure following settings in Azure before scanning.
-- Create a container
+To put results in Azure Blob Storage, configure following settings in Azure before reporting.
+- Create a Azure Blob container
 
 ```
 $ vuls scan \
-      -cve-dictionary-dbpath=$PWD/cve.sqlite3 \
+      -cvedb-path=$PWD/cve.sqlite3 \
       -report-azure-blob \
       -azure-container=vuls \
       -azure-account=test \
       -azure-key=access-key-string
 ```
 With this sample command, it will ..
-- Use SSH Key-Based authentication with empty password (without -ask-key-password option)
-- Scan all servers defined in config file
 - Put scan result(JSON) in Azure Blob Storage. The container name is "vuls", storage account is "test" and accesskey is "access-key-string"
 
 account and access key can be defined in environment variables.
@@ -800,14 +906,14 @@ account and access key can be defined in environment variables.
 $ export AZURE_STORAGE_ACCOUNT=test
 $ export AZURE_STORAGE_ACCESS_KEY=access-key-string
 $ vuls scan \
-      -cve-dictionary-dbpath=$PWD/cve.sqlite3 \
+      -cvedb-path=$PWD/cve.sqlite3 \
       -report-azure-blob \
       -azure-container=vuls
 ```
 
 ## Example: IgnoreCves
 
-Define ignoreCves in config if you don't want to report(slack, mail, text...) specific CVE IDs. But these ignoreCves will be output to JSON file like below.
+Define ignoreCves in config if you don't want to report(Slack, EMail, Text...) specific CVE IDs. But these ignoreCves will be output to JSON file like below.
 
 - config.toml
 ```toml
@@ -886,8 +992,8 @@ optional = [
 
 ```
 $ vuls scan \
-      -cve-dictionary-dbtype=mysql \
-      -cve-dictionary-dbpath="user:pass@tcp(localhost:3306)/dbname?parseTime=true"
+      -cvedb-type=mysql \
+      -cvedb-url="user:pass@tcp(localhost:3306)/dbname?parseTime=true"
 ```
 
 ----
@@ -941,42 +1047,6 @@ How to integrate Vuls with OWASP Dependency Check
     ```
 
 
-# Usage: Scan Docker containers
-
-It is common that keep Docker containers running without SSHd daemon.  
-see [Docker Blog:Why you don't need to run SSHd in your Docker containers](https://blog.docker.com/2014/06/why-you-dont-need-to-run-sshd-in-docker/)
-
-Vuls scans Docker containers via `docker exec` instead of SSH.  
-For more details, see [Architecture section](https://github.com/future-architect/vuls#architecture)
-
-- To scan all of running containers  
-  `"${running}"` needs to be set in the containers item.
-    ```
-    [servers]
-
-    [servers.172-31-4-82]
-    host         = "172.31.4.82"
-    user        = "ec2-user"
-    keyPath     = "/home/username/.ssh/id_rsa"
-    containers = ["${running}"]
-    ```
-
-- To scan specific containers  
-  The container ID or container name needs to be set in the containers item.  
-  In the following example, only `container_name_a` and `4aa37a8b63b9` will be scanned.  
-  Be sure to check these containers are running state before scanning.  
-  If specified containers are not running, Vuls gives up scanning with printing error message.
-    ```
-    [servers]
-
-    [servers.172-31-4-82]
-    host         = "172.31.4.82"
-    user        = "ec2-user"
-    keyPath     = "/home/username/.ssh/id_rsa"
-    containers = ["container_name_a", "4aa37a8b63b9"]
-    ```
-- To scan containers only
-  - --containers-only option is available.
 
 
 # Usage: TUI
@@ -984,15 +1054,27 @@ For more details, see [Architecture section](https://github.com/future-architect
 ## Display the latest scan results
 
 ```
-$ vuls tui -h
 tui:
-	tui [-results-dir=/path/to/results]
+        tui
+                [-cvedb-type=sqlite3|mysql]
+                [-cvedb-path=/path/to/cve.sqlite3]
+                [-cvedb-url=http://127.0.0.1:1323 or mysql connection string]
+                [-results-dir=/path/to/results]
+                [-refresh-cve]
+                [-debug-sql]
 
-  -results-dir string
-        /path/to/results (default "$PWD/results")
+  -cvedb-path string
+        /path/to/sqlite3 (For get cve detail from cve.sqlite3) (default "/Users/kotakanbe/go/src/github.com/future-architect/vuls/cve.sqlite3")
+  -cvedb-type string
+        DB type for fetching CVE dictionary (sqlite3 or mysql) (default "sqlite3")
+  -cvedb-url string
+        http://cve-dictionary.com:8080 or mysql connection string
   -debug-sql
-    	debug SQL
-
+        debug SQL
+  -refresh-cve
+        Refresh CVE information in JSON file under results dir
+  -results-dir string
+        /path/to/results (default "/Users/kotakanbe/go/src/github.com/future-architect/vuls/results")
 ```
 
 Key binding is below.
@@ -1011,18 +1093,14 @@ For details, see https://github.com/future-architect/vuls/blob/master/report/tui
 - Display the list of scan results.
 ```
 $ vuls history
-20160524_1950 scanned 1 servers: amazon2
-20160524_1940 scanned 2 servers: amazon1, romantic_goldberg
+2016-12-30T10:34:38+09:00 1 servers: u16
+2016-12-28T19:15:19+09:00 1 servers: ama
+2016-12-28T19:10:03+09:00 1 servers: cent6
 ```
 
-- Display the result of scan 20160524_1949
+- Display the result of scan 2016-12-30T10:34:38+09:00
 ```
-$ vuls tui 20160524_1950
-```
-
-- Display the result of scan 20160524_1948
-```
-$ vuls tui 20160524_1940
+$ vuls tui 2016-12-30T10:34:38+09:00
 ```
 
 # Display the previous scan results using peco
@@ -1040,10 +1118,10 @@ Run go-cve-dictionary as server mode before scanning on 192.168.10.1
 $ go-cve-dictionary server -bind=192.168.10.1 -port=1323
 ```
 
-Run Vuls with -cve-dictionary-url option.
+Run Vuls with -cvedb-url option.
 
 ```
-$ vuls scan -cve-dictionary-url=http://192.168.0.1:1323
+$ vuls scan -cvedb-url=http://192.168.0.1:1323
 ```
 
 # Usage: Update NVD Data
diff --git a/commands/configtest.go b/commands/configtest.go
index e80f6f4f..2bcda750 100644
--- a/commands/configtest.go
+++ b/commands/configtest.go
@@ -146,7 +146,7 @@ func (p *ConfigtestCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interfa
 	Log := util.NewCustomLogger(c.ServerInfo{})
 
 	Log.Info("Validating Config...")
-	if !c.Conf.Validate() {
+	if !c.Conf.ValidateOnConfigtest() {
 		return subcommands.ExitUsageError
 	}
 
diff --git a/commands/discover.go b/commands/discover.go
index 4aa32e2f..7d674250 100644
--- a/commands/discover.go
+++ b/commands/discover.go
@@ -98,7 +98,7 @@ iconEmoji    = ":ghost:"
 authUser     = "username"
 notifyUsers  = ["@username"]
 
-[mail]
+[email]
 smtpAddr      = "smtp.gmail.com"
 smtpPort      = "587"
 user          = "username"
diff --git a/commands/history.go b/commands/history.go
index 95932866..1c6acf4b 100644
--- a/commands/history.go
+++ b/commands/history.go
@@ -27,7 +27,6 @@ import (
 	"strings"
 
 	c "github.com/future-architect/vuls/config"
-	"github.com/future-architect/vuls/report"
 	"github.com/google/subcommands"
 )
 
@@ -70,11 +69,11 @@ func (p *HistoryCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{
 	c.Conf.ResultsDir = p.resultsDir
 
 	var err error
-	var jsonDirs report.JSONDirs
-	if jsonDirs, err = report.GetValidJSONDirs(); err != nil {
+	var dirs jsonDirs
+	if dirs, err = lsValidJSONDirs(); err != nil {
 		return subcommands.ExitFailure
 	}
-	for _, d := range jsonDirs {
+	for _, d := range dirs {
 		var files []os.FileInfo
 		if files, err = ioutil.ReadDir(d); err != nil {
 			return subcommands.ExitFailure
@@ -89,7 +88,7 @@ func (p *HistoryCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{
 		}
 		splitPath := strings.Split(d, string(os.PathSeparator))
 		timeStr := splitPath[len(splitPath)-1]
-		fmt.Printf("%s scanned %d servers: %s\n",
+		fmt.Printf("%s %d servers: %s\n",
 			timeStr,
 			len(hosts),
 			strings.Join(hosts, ", "),
diff --git a/commands/prepare.go b/commands/prepare.go
index 02389ea4..cf1519ac 100644
--- a/commands/prepare.go
+++ b/commands/prepare.go
@@ -50,8 +50,9 @@ func (*PrepareCmd) Synopsis() string {
 	return `Install required packages to scan.
 				CentOS: yum-plugin-security, yum-plugin-changelog
 				Amazon: None
-				RHEL:   TODO
+				RHEL:   None
 				Ubuntu: None
+				Debian: aptitude
 
 	`
 }
@@ -155,7 +156,7 @@ func (p *PrepareCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{
 	c.Conf.AssumeYes = p.assumeYes
 
 	logrus.Info("Validating Config...")
-	if !c.Conf.Validate() {
+	if !c.Conf.ValidateOnPrepare() {
 		return subcommands.ExitUsageError
 	}
 	// Set up custom logger
diff --git a/commands/report.go b/commands/report.go
new file mode 100644
index 00000000..17095dcc
--- /dev/null
+++ b/commands/report.go
@@ -0,0 +1,386 @@
+/* Vuls - Vulnerability Scanner
+Copyright (C) 2016  Future Architect, Inc. Japan.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see .
+*/
+
+package commands
+
+import (
+	"context"
+	"flag"
+	"fmt"
+	"os"
+	"path/filepath"
+
+	c "github.com/future-architect/vuls/config"
+	"github.com/future-architect/vuls/cveapi"
+	"github.com/future-architect/vuls/models"
+	"github.com/future-architect/vuls/report"
+	"github.com/future-architect/vuls/util"
+	"github.com/google/subcommands"
+	"github.com/kotakanbe/go-cve-dictionary/log"
+)
+
+// ReportCmd is subcommand for reporting
+type ReportCmd struct {
+	lang       string
+	debug      bool
+	debugSQL   bool
+	configPath string
+	resultsDir string
+	refreshCve bool
+
+	cvssScoreOver      float64
+	ignoreUnscoredCves bool
+	httpProxy          string
+
+	cvedbtype        string
+	cvedbpath        string
+	cveDictionaryURL string
+
+	toSlack     bool
+	toEMail     bool
+	toLocalFile bool
+	toS3        bool
+	toAzureBlob bool
+
+	formatJSON        bool
+	formatXML         bool
+	formatOneLineText bool
+	formatShortText   bool
+	formatFullText    bool
+
+	gzip bool
+
+	awsProfile  string
+	awsS3Bucket string
+	awsRegion   string
+
+	azureAccount   string
+	azureKey       string
+	azureContainer string
+}
+
+// Name return subcommand name
+func (*ReportCmd) Name() string { return "report" }
+
+// Synopsis return synopsis
+func (*ReportCmd) Synopsis() string { return "Reporting" }
+
+// Usage return usage
+func (*ReportCmd) Usage() string {
+	return `report:
+	report
+		[-lang=en|ja]
+		[-config=/path/to/config.toml]
+		[-results-dir=/path/to/results]
+		[-refresh-cve]
+		[-cvedb-type=sqlite3|mysql]
+		[-cvedb-path=/path/to/cve.sqlite3]
+		[-cvedb-url=http://127.0.0.1:1323 or mysql connection string]
+		[-cvss-over=7]
+		[-ignore-unscored-cves]
+		[-to-email]
+		[-to-slack]
+		[-to-localfile]
+		[-to-s3]
+		[-to-azure-blob]
+		[-format-json]
+		[-format-xml]
+		[-format-one-line-text]
+		[-format-short-text]
+		[-format-full-text]
+		[-gzip]
+		[-aws-profile=default]
+		[-aws-region=us-west-2]
+		[-aws-s3-bucket=bucket_name]
+		[-azure-account=accout]
+		[-azure-key=key]
+		[-azure-container=container]
+		[-http-proxy=http://192.168.0.1:8080]
+		[-debug]
+		[-debug-sql]
+
+		[SERVER]...
+`
+}
+
+// SetFlags set flag
+func (p *ReportCmd) SetFlags(f *flag.FlagSet) {
+	f.StringVar(&p.lang, "lang", "en", "[en|ja]")
+	f.BoolVar(&p.debug, "debug", false, "debug mode")
+	f.BoolVar(&p.debugSQL, "debug-sql", false, "SQL debug mode")
+
+	wd, _ := os.Getwd()
+
+	defaultConfPath := filepath.Join(wd, "config.toml")
+	f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
+
+	defaultResultsDir := filepath.Join(wd, "results")
+	f.StringVar(&p.resultsDir, "results-dir", defaultResultsDir, "/path/to/results")
+
+	f.BoolVar(
+		&p.refreshCve,
+		"refresh-cve",
+		false,
+		"Refresh CVE information in JSON file under results dir")
+
+	f.StringVar(
+		&p.cvedbtype,
+		"cvedb-type",
+		"sqlite3",
+		"DB type for fetching CVE dictionary (sqlite3 or mysql)")
+
+	defaultCveDBPath := filepath.Join(wd, "cve.sqlite3")
+	f.StringVar(
+		&p.cvedbpath,
+		"cvedb-path",
+		defaultCveDBPath,
+		"/path/to/sqlite3 (For get cve detail from cve.sqlite3)")
+
+	f.StringVar(
+		&p.cveDictionaryURL,
+		"cvedb-url",
+		"",
+		"http://cve-dictionary.com:8080 or mysql connection string")
+
+	f.Float64Var(
+		&p.cvssScoreOver,
+		"cvss-over",
+		0,
+		"-cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))")
+
+	f.BoolVar(
+		&p.ignoreUnscoredCves,
+		"ignore-unscored-cves",
+		false,
+		"Don't report the unscored CVEs")
+
+	f.StringVar(
+		&p.httpProxy,
+		"http-proxy",
+		"",
+		"http://proxy-url:port (default: empty)")
+
+	f.BoolVar(&p.formatJSON,
+		"format-json",
+		false,
+		fmt.Sprintf("JSON format"))
+
+	f.BoolVar(&p.formatXML,
+		"format-xml",
+		false,
+		fmt.Sprintf("XML format"))
+
+	f.BoolVar(&p.formatOneLineText,
+		"format-one-line-text",
+		false,
+		fmt.Sprintf("One line summary in plain text"))
+
+	f.BoolVar(&p.formatShortText,
+		"format-short-text",
+		false,
+		fmt.Sprintf("Summary in plain text"))
+
+	f.BoolVar(&p.formatFullText,
+		"format-full-text",
+		false,
+		fmt.Sprintf("Detail report in plain text"))
+
+	f.BoolVar(&p.gzip, "gzip", false, "gzip compression")
+
+	f.BoolVar(&p.toSlack, "to-slack", false, "Send report via Slack")
+	f.BoolVar(&p.toEMail, "to-email", false, "Send report via Email")
+	f.BoolVar(&p.toLocalFile,
+		"to-localfile",
+		false,
+		fmt.Sprintf("Write report to localfile"))
+
+	f.BoolVar(&p.toS3,
+		"to-s3",
+		false,
+		"Write report to S3 (bucket/yyyyMMdd_HHmm/servername.json/xml/txt)")
+	f.StringVar(&p.awsProfile, "aws-profile", "default", "AWS profile to use")
+	f.StringVar(&p.awsRegion, "aws-region", "us-east-1", "AWS region to use")
+	f.StringVar(&p.awsS3Bucket, "aws-s3-bucket", "", "S3 bucket name")
+
+	f.BoolVar(&p.toAzureBlob,
+		"to-azure-blob",
+		false,
+		"Write report to Azure Storage blob (container/yyyyMMdd_HHmm/servername.json/xml/txt)")
+	f.StringVar(&p.azureAccount,
+		"azure-account",
+		"",
+		"Azure account name to use. AZURE_STORAGE_ACCOUNT environment variable is used if not specified")
+	f.StringVar(&p.azureKey,
+		"azure-key",
+		"",
+		"Azure account key to use. AZURE_STORAGE_ACCESS_KEY environment variable is used if not specified")
+	f.StringVar(&p.azureContainer, "azure-container", "", "Azure storage container name")
+}
+
+// Execute execute
+func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
+	c.Conf.Debug = p.debug
+	c.Conf.DebugSQL = p.debugSQL
+	Log := util.NewCustomLogger(c.ServerInfo{})
+
+	if err := c.Load(p.configPath, ""); err != nil {
+		Log.Errorf("Error loading %s, %s", p.configPath, err)
+		return subcommands.ExitUsageError
+	}
+
+	c.Conf.Lang = p.lang
+	c.Conf.ResultsDir = p.resultsDir
+	c.Conf.CveDBType = p.cvedbtype
+	c.Conf.CveDBPath = p.cvedbpath
+	c.Conf.CveDictionaryURL = p.cveDictionaryURL
+	c.Conf.CvssScoreOver = p.cvssScoreOver
+	c.Conf.IgnoreUnscoredCves = p.ignoreUnscoredCves
+	c.Conf.HTTPProxy = p.httpProxy
+
+	jsonDir, err := jsonDir(f.Args())
+	if err != nil {
+		log.Errorf("Failed to read from JSON: %s", err)
+		return subcommands.ExitFailure
+	}
+
+	c.Conf.FormatXML = p.formatXML
+	c.Conf.FormatJSON = p.formatJSON
+	c.Conf.FormatOneLineText = p.formatOneLineText
+	c.Conf.FormatShortText = p.formatShortText
+	c.Conf.FormatFullText = p.formatFullText
+
+	c.Conf.GZIP = p.gzip
+
+	// report
+	reports := []report.ResultWriter{
+		report.StdoutWriter{},
+	}
+
+	if p.toSlack {
+		reports = append(reports, report.SlackWriter{})
+	}
+
+	if p.toEMail {
+		reports = append(reports, report.EMailWriter{})
+	}
+
+	if p.toLocalFile {
+		reports = append(reports, report.LocalFileWriter{
+			CurrentDir: jsonDir,
+		})
+	}
+
+	if p.toS3 {
+		c.Conf.AwsRegion = p.awsRegion
+		c.Conf.AwsProfile = p.awsProfile
+		c.Conf.S3Bucket = p.awsS3Bucket
+		if err := report.CheckIfBucketExists(); err != nil {
+			Log.Errorf("Check if there is a bucket beforehand: %s, err: %s", c.Conf.S3Bucket, err)
+			return subcommands.ExitUsageError
+		}
+		reports = append(reports, report.S3Writer{})
+	}
+
+	if p.toAzureBlob {
+		c.Conf.AzureAccount = p.azureAccount
+		if len(c.Conf.AzureAccount) == 0 {
+			c.Conf.AzureAccount = os.Getenv("AZURE_STORAGE_ACCOUNT")
+		}
+
+		c.Conf.AzureKey = p.azureKey
+		if len(c.Conf.AzureKey) == 0 {
+			c.Conf.AzureKey = os.Getenv("AZURE_STORAGE_ACCESS_KEY")
+		}
+
+		c.Conf.AzureContainer = p.azureContainer
+		if len(c.Conf.AzureContainer) == 0 {
+			Log.Error("Azure storage container name is requied with --azure-container option")
+			return subcommands.ExitUsageError
+		}
+		if err := report.CheckIfAzureContainerExists(); err != nil {
+			Log.Errorf("Check if there is a container beforehand: %s, err: %s", c.Conf.AzureContainer, err)
+			return subcommands.ExitUsageError
+		}
+		reports = append(reports, report.AzureBlobWriter{})
+	}
+
+	if !(p.formatJSON || p.formatOneLineText ||
+		p.formatShortText || p.formatFullText || p.formatXML) {
+		c.Conf.FormatShortText = true
+	}
+
+	Log.Info("Validating Config...")
+	if !c.Conf.ValidateOnReport() {
+		return subcommands.ExitUsageError
+	}
+	if ok, err := cveapi.CveClient.CheckHealth(); !ok {
+		Log.Errorf("CVE HTTP server is not running. err: %s", err)
+		Log.Errorf("Run go-cve-dictionary as server mode before reporting or run with --cvedb-path option")
+		return subcommands.ExitFailure
+	}
+	if c.Conf.CveDictionaryURL != "" {
+		Log.Infof("cve-dictionary: %s", c.Conf.CveDictionaryURL)
+	} else {
+		if c.Conf.CveDBType == "sqlite3" {
+			Log.Infof("cve-dictionary: %s", c.Conf.CveDBPath)
+		}
+	}
+
+	history, err := loadOneScanHistory(jsonDir)
+
+	var results []models.ScanResult
+	for _, r := range history.ScanResults {
+		if p.refreshCve || needToRefreshCve(r) {
+			Log.Debugf("need to refresh")
+			if c.Conf.CveDBType == "sqlite3" {
+				if _, err := os.Stat(c.Conf.CveDBPath); os.IsNotExist(err) {
+					log.Errorf("SQLite3 DB(CVE-Dictionary) is not exist: %s",
+						c.Conf.CveDBPath)
+					return subcommands.ExitFailure
+				}
+			}
+
+			filled, err := fillCveInfoFromCveDB(r)
+			if err != nil {
+				Log.Errorf("Failed to fill CVE information: %s", err)
+				return subcommands.ExitFailure
+			}
+			filled.Lang = c.Conf.Lang
+
+			if err := overwriteJSONFile(jsonDir, filled); err != nil {
+				Log.Errorf("Failed to write JSON: %s", err)
+				return subcommands.ExitFailure
+			}
+			results = append(results, filled)
+		} else {
+			Log.Debugf("no need to refresh")
+			results = append(results, r)
+		}
+	}
+
+	var res models.ScanResults
+	for _, r := range results {
+		res = append(res, r.FilterByCvssOver())
+	}
+	for _, w := range reports {
+		if err := w.Write(res...); err != nil {
+			Log.Errorf("Failed to report: %s", err)
+			return subcommands.ExitFailure
+		}
+	}
+	return subcommands.ExitSuccess
+}
diff --git a/commands/scan.go b/commands/scan.go
index 17ebd7fa..f06316bb 100644
--- a/commands/scan.go
+++ b/commands/scan.go
@@ -25,12 +25,9 @@ import (
 	"os"
 	"path/filepath"
 	"strings"
-	"time"
 
 	"github.com/Sirupsen/logrus"
 	c "github.com/future-architect/vuls/config"
-	"github.com/future-architect/vuls/cveapi"
-	"github.com/future-architect/vuls/report"
 	"github.com/future-architect/vuls/scan"
 	"github.com/future-architect/vuls/util"
 	"github.com/google/subcommands"
@@ -39,46 +36,15 @@ import (
 
 // ScanCmd is Subcommand of host discovery mode
 type ScanCmd struct {
-	lang     string
-	debug    bool
-	debugSQL bool
-
-	configPath string
-
-	resultsDir       string
-	cvedbtype        string
-	cvedbpath        string
-	cveDictionaryURL string
-	cacheDBPath      string
-
-	cvssScoreOver      float64
-	ignoreUnscoredCves bool
-
-	httpProxy       string
-	askSudoPassword bool
-	askKeyPassword  bool
-
+	debug          bool
+	configPath     string
+	resultsDir     string
+	cacheDBPath    string
+	httpProxy      string
+	askKeyPassword bool
 	containersOnly bool
 	skipBroken     bool
-
-	// reporting
-	reportSlack     bool
-	reportMail      bool
-	reportJSON      bool
-	reportText      bool
-	reportS3        bool
-	reportAzureBlob bool
-	reportXML       bool
-
-	awsProfile  string
-	awsS3Bucket string
-	awsRegion   string
-
-	azureAccount   string
-	azureKey       string
-	azureContainer string
-
-	sshExternal bool
+	sshExternal    bool
 }
 
 // Name return subcommand name
@@ -91,35 +57,15 @@ func (*ScanCmd) Synopsis() string { return "Scan vulnerabilities" }
 func (*ScanCmd) Usage() string {
 	return `scan:
 	scan
-		[-lang=en|ja]
 		[-config=/path/to/config.toml]
 		[-results-dir=/path/to/results]
-		[-cve-dictionary-dbtype=sqlite3|mysql]
-		[-cve-dictionary-dbpath=/path/to/cve.sqlite3 or mysql connection string]
-		[-cve-dictionary-url=http://127.0.0.1:1323]
-		[-cache-dbpath=/path/to/cache.db]
-		[-cvss-over=7]
-		[-ignore-unscored-cves]
+		[-cachedb-path=/path/to/cache.db]
 		[-ssh-external]
 		[-containers-only]
 		[-skip-broken]
-		[-report-azure-blob]
-		[-report-json]
-		[-report-mail]
-		[-report-s3]
-		[-report-slack]
-		[-report-text]
-		[-report-xml]
 		[-http-proxy=http://192.168.0.1:8080]
 		[-ask-key-password]
 		[-debug]
-		[-debug-sql]
-		[-aws-profile=default]
-		[-aws-region=us-west-2]
-		[-aws-s3-bucket=bucket_name]
-		[-azure-account=accout]
-		[-azure-key=key]
-		[-azure-container=container]
 
 		[SERVER]...
 `
@@ -127,9 +73,7 @@ func (*ScanCmd) Usage() string {
 
 // SetFlags set flag
 func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
-	f.StringVar(&p.lang, "lang", "en", "[en|ja]")
 	f.BoolVar(&p.debug, "debug", false, "debug mode")
-	f.BoolVar(&p.debugSQL, "debug-sql", false, "SQL debug mode")
 
 	wd, _ := os.Getwd()
 
@@ -139,44 +83,13 @@ func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
 	defaultResultsDir := filepath.Join(wd, "results")
 	f.StringVar(&p.resultsDir, "results-dir", defaultResultsDir, "/path/to/results")
 
-	f.StringVar(
-		&p.cvedbtype,
-		"cve-dictionary-dbtype",
-		"sqlite3",
-		"DB type for fetching CVE dictionary (sqlite3 or mysql)")
-
-	f.StringVar(
-		&p.cvedbpath,
-		"cve-dictionary-dbpath",
-		"",
-		"/path/to/sqlite3 (For get cve detail from cve.sqlite3)")
-
-	defaultURL := "http://127.0.0.1:1323"
-	f.StringVar(
-		&p.cveDictionaryURL,
-		"cve-dictionary-url",
-		defaultURL,
-		"http://CVE.Dictionary")
-
 	defaultCacheDBPath := filepath.Join(wd, "cache.db")
 	f.StringVar(
 		&p.cacheDBPath,
-		"cache-dbpath",
+		"cachedb-path",
 		defaultCacheDBPath,
 		"/path/to/cache.db (local cache of changelog for Ubuntu/Debian)")
 
-	f.Float64Var(
-		&p.cvssScoreOver,
-		"cvss-over",
-		0,
-		"-cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))")
-
-	f.BoolVar(
-		&p.ignoreUnscoredCves,
-		"ignore-unscored-cves",
-		false,
-		"Don't report the unscored CVEs")
-
 	f.BoolVar(
 		&p.sshExternal,
 		"ssh-external",
@@ -202,55 +115,12 @@ func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
 		"http://proxy-url:port (default: empty)",
 	)
 
-	f.BoolVar(&p.reportSlack, "report-slack", false, "Send report via Slack")
-	f.BoolVar(&p.reportMail, "report-mail", false, "Send report via Email")
-	f.BoolVar(&p.reportJSON,
-		"report-json",
-		false,
-		fmt.Sprintf("Write report to JSON files (%s/results/current)", wd),
-	)
-	f.BoolVar(&p.reportText,
-		"report-text",
-		false,
-		fmt.Sprintf("Write report to text files (%s/results/current)", wd),
-	)
-	f.BoolVar(&p.reportXML,
-		"report-xml",
-		false,
-		fmt.Sprintf("Write report to XML files (%s/results/current)", wd),
-	)
-
-	f.BoolVar(&p.reportS3,
-		"report-s3",
-		false,
-		"Write report to S3 (bucket/yyyyMMdd_HHmm/servername.json)",
-	)
-	f.StringVar(&p.awsProfile, "aws-profile", "default", "AWS profile to use")
-	f.StringVar(&p.awsRegion, "aws-region", "us-east-1", "AWS region to use")
-	f.StringVar(&p.awsS3Bucket, "aws-s3-bucket", "", "S3 bucket name")
-
-	f.BoolVar(&p.reportAzureBlob,
-		"report-azure-blob",
-		false,
-		"Write report to Azure Storage blob (container/yyyyMMdd_HHmm/servername.json)",
-	)
-	f.StringVar(&p.azureAccount, "azure-account", "", "Azure account name to use. AZURE_STORAGE_ACCOUNT environment variable is used if not specified")
-	f.StringVar(&p.azureKey, "azure-key", "", "Azure account key to use. AZURE_STORAGE_ACCESS_KEY environment variable is used if not specified")
-	f.StringVar(&p.azureContainer, "azure-container", "", "Azure storage container name")
-
 	f.BoolVar(
 		&p.askKeyPassword,
 		"ask-key-password",
 		false,
 		"Ask ssh privatekey password before scanning",
 	)
-
-	f.BoolVar(
-		&p.askSudoPassword,
-		"ask-sudo-password",
-		false,
-		"[Deprecated] THIS OPTION WAS REMOVED FOR SECURITY REASONS. Define NOPASSWD in /etc/sudoers on target servers and use SSH key-based authentication",
-	)
 }
 
 // Execute execute
@@ -264,10 +134,6 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
 			return subcommands.ExitFailure
 		}
 	}
-	if p.askSudoPassword {
-		logrus.Errorf("[Deprecated] -ask-sudo-password WAS REMOVED FOR SECURITY REASONS. Define NOPASSWD in /etc/sudoers on target servers and use SSH key-based authentication")
-		return subcommands.ExitFailure
-	}
 
 	c.Conf.Debug = p.debug
 	err = c.Load(p.configPath, keyPass)
@@ -278,13 +144,6 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
 
 	logrus.Info("Start scanning")
 	logrus.Infof("config: %s", p.configPath)
-	if p.cvedbpath != "" {
-		if p.cvedbtype == "sqlite3" {
-			logrus.Infof("cve-dictionary: %s", p.cvedbpath)
-		}
-	} else {
-		logrus.Infof("cve-dictionary: %s", p.cveDictionaryURL)
-	}
 
 	var servernames []string
 	if 0 < len(f.Args()) {
@@ -324,91 +183,21 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
 	}
 	logrus.Debugf("%s", pp.Sprintf("%v", target))
 
-	c.Conf.Lang = p.lang
-	c.Conf.DebugSQL = p.debugSQL
-
 	// logger
 	Log := util.NewCustomLogger(c.ServerInfo{})
-	scannedAt := time.Now()
-
-	// report
-	reports := []report.ResultWriter{
-		report.StdoutWriter{},
-		report.LogrusWriter{},
-	}
-	if p.reportSlack {
-		reports = append(reports, report.SlackWriter{})
-	}
-	if p.reportMail {
-		reports = append(reports, report.MailWriter{})
-	}
-	if p.reportJSON {
-		reports = append(reports, report.JSONWriter{ScannedAt: scannedAt})
-	}
-	if p.reportText {
-		reports = append(reports, report.TextFileWriter{ScannedAt: scannedAt})
-	}
-	if p.reportXML {
-		reports = append(reports, report.XMLWriter{ScannedAt: scannedAt})
-	}
-	if p.reportS3 {
-		c.Conf.AwsRegion = p.awsRegion
-		c.Conf.AwsProfile = p.awsProfile
-		c.Conf.S3Bucket = p.awsS3Bucket
-		if err := report.CheckIfBucketExists(); err != nil {
-			Log.Errorf("Failed to access to the S3 bucket. err: %s", err)
-			Log.Error("Ensure the bucket or check AWS config before scanning")
-			return subcommands.ExitUsageError
-		}
-		reports = append(reports, report.S3Writer{})
-	}
-	if p.reportAzureBlob {
-		c.Conf.AzureAccount = p.azureAccount
-		if len(c.Conf.AzureAccount) == 0 {
-			c.Conf.AzureAccount = os.Getenv("AZURE_STORAGE_ACCOUNT")
-		}
-
-		c.Conf.AzureKey = p.azureKey
-		if len(c.Conf.AzureKey) == 0 {
-			c.Conf.AzureKey = os.Getenv("AZURE_STORAGE_ACCESS_KEY")
-		}
-
-		c.Conf.AzureContainer = p.azureContainer
-		if len(c.Conf.AzureContainer) == 0 {
-			Log.Error("Azure storage container name is requied with --azure-container option")
-			return subcommands.ExitUsageError
-		}
-		if err := report.CheckIfAzureContainerExists(); err != nil {
-			Log.Errorf("Failed to access to the Azure Blob container. err: %s", err)
-			Log.Error("Ensure the container or check Azure config before scanning")
-			return subcommands.ExitUsageError
-		}
-		reports = append(reports, report.AzureBlobWriter{})
-	}
 
 	c.Conf.ResultsDir = p.resultsDir
-	c.Conf.CveDBType = p.cvedbtype
-	c.Conf.CveDBPath = p.cvedbpath
-	c.Conf.CveDictionaryURL = p.cveDictionaryURL
 	c.Conf.CacheDBPath = p.cacheDBPath
-	c.Conf.CvssScoreOver = p.cvssScoreOver
-	c.Conf.IgnoreUnscoredCves = p.ignoreUnscoredCves
 	c.Conf.SSHExternal = p.sshExternal
 	c.Conf.HTTPProxy = p.httpProxy
 	c.Conf.ContainersOnly = p.containersOnly
 	c.Conf.SkipBroken = p.skipBroken
 
 	Log.Info("Validating Config...")
-	if !c.Conf.Validate() {
+	if !c.Conf.ValidateOnScan() {
 		return subcommands.ExitUsageError
 	}
 
-	if ok, err := cveapi.CveClient.CheckHealth(); !ok {
-		Log.Errorf("CVE HTTP server is not running. err: %s", err)
-		Log.Errorf("Run go-cve-dictionary as server mode or specify -cve-dictionary-dbpath option")
-		return subcommands.ExitFailure
-	}
-
 	Log.Info("Detecting Server/Contianer OS... ")
 	if err := scan.InitServers(Log); err != nil {
 		Log.Errorf("Failed to init servers: %s", err)
@@ -431,21 +220,9 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
 		}
 		return subcommands.ExitFailure
 	}
-
-	scanResults, err := scan.GetScanResults()
-	if err != nil {
-		Log.Fatal(err)
-		return subcommands.ExitFailure
-	}
-
-	Log.Info("Reporting...")
-	filtered := scanResults.FilterByCvssOver()
-	for _, w := range reports {
-		if err := w.Write(filtered); err != nil {
-			Log.Fatalf("Failed to report, err: %s", err)
-			return subcommands.ExitFailure
-		}
-	}
+	fmt.Printf("\n\n\n")
+	fmt.Println("To view the detail, vuls tui is useful.")
+	fmt.Println("To send a report, run vuls report -h.")
 
 	return subcommands.ExitSuccess
 }
diff --git a/commands/tui.go b/commands/tui.go
index 1b9cd53f..19a4d740 100644
--- a/commands/tui.go
+++ b/commands/tui.go
@@ -20,13 +20,12 @@ package commands
 import (
 	"context"
 	"flag"
-	"io/ioutil"
 	"os"
 	"path/filepath"
-	"strings"
 
 	log "github.com/Sirupsen/logrus"
 	c "github.com/future-architect/vuls/config"
+	"github.com/future-architect/vuls/models"
 	"github.com/future-architect/vuls/report"
 	"github.com/google/subcommands"
 )
@@ -36,6 +35,11 @@ type TuiCmd struct {
 	lang       string
 	debugSQL   bool
 	resultsDir string
+
+	refreshCve       bool
+	cvedbtype        string
+	cvedbpath        string
+	cveDictionaryURL string
 }
 
 // Name return subcommand name
@@ -47,7 +51,13 @@ func (*TuiCmd) Synopsis() string { return "Run Tui view to anayze vulnerabilites
 // Usage return usage
 func (*TuiCmd) Usage() string {
 	return `tui:
-	tui [-results-dir=/path/to/results]
+	tui
+		[-cvedb-type=sqlite3|mysql]
+		[-cvedb-path=/path/to/cve.sqlite3]
+		[-cvedb-url=http://127.0.0.1:1323 or mysql connection string]
+		[-results-dir=/path/to/results]
+		[-refresh-cve]
+		[-debug-sql]
 
 `
 }
@@ -58,9 +68,33 @@ func (p *TuiCmd) SetFlags(f *flag.FlagSet) {
 	f.BoolVar(&p.debugSQL, "debug-sql", false, "debug SQL")
 
 	wd, _ := os.Getwd()
-
 	defaultResultsDir := filepath.Join(wd, "results")
 	f.StringVar(&p.resultsDir, "results-dir", defaultResultsDir, "/path/to/results")
+
+	f.BoolVar(
+		&p.refreshCve,
+		"refresh-cve",
+		false,
+		"Refresh CVE information in JSON file under results dir")
+
+	f.StringVar(
+		&p.cvedbtype,
+		"cvedb-type",
+		"sqlite3",
+		"DB type for fetching CVE dictionary (sqlite3 or mysql)")
+
+	defaultCveDBPath := filepath.Join(wd, "cve.sqlite3")
+	f.StringVar(
+		&p.cvedbpath,
+		"cvedb-path",
+		defaultCveDBPath,
+		"/path/to/sqlite3 (For get cve detail from cve.sqlite3)")
+
+	f.StringVar(
+		&p.cveDictionaryURL,
+		"cvedb-url",
+		"",
+		"http://cve-dictionary.com:8080 or mysql connection string")
 }
 
 // Execute execute
@@ -68,38 +102,53 @@ func (p *TuiCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) s
 	c.Conf.Lang = "en"
 	c.Conf.DebugSQL = p.debugSQL
 	c.Conf.ResultsDir = p.resultsDir
+	c.Conf.CveDBType = p.cvedbtype
+	c.Conf.CveDBPath = p.cvedbpath
+	c.Conf.CveDictionaryURL = p.cveDictionaryURL
 
-	var jsonDirName string
-	var err error
-	if 0 < len(f.Args()) {
-		var jsonDirs report.JSONDirs
-		if jsonDirs, err = report.GetValidJSONDirs(); err != nil {
-			return subcommands.ExitFailure
-		}
-		for _, d := range jsonDirs {
-			splitPath := strings.Split(d, string(os.PathSeparator))
-			if splitPath[len(splitPath)-1] == f.Args()[0] {
-				jsonDirName = f.Args()[0]
-				break
+	log.Info("Validating Config...")
+	if !c.Conf.ValidateOnTui() {
+		return subcommands.ExitUsageError
+	}
+
+	jsonDir, err := jsonDir(f.Args())
+	if err != nil {
+		log.Errorf("Failed to read json dir under results: %s", err)
+		return subcommands.ExitFailure
+	}
+
+	history, err := loadOneScanHistory(jsonDir)
+	if err != nil {
+		log.Errorf("Failed to read from JSON: %s", err)
+		return subcommands.ExitFailure
+	}
+
+	var results []models.ScanResult
+	for _, r := range history.ScanResults {
+		if p.refreshCve || needToRefreshCve(r) {
+			if c.Conf.CveDBType == "sqlite3" {
+				if _, err := os.Stat(c.Conf.CveDBPath); os.IsNotExist(err) {
+					log.Errorf("SQLite3 DB(CVE-Dictionary) is not exist: %s",
+						c.Conf.CveDBPath)
+					return subcommands.ExitFailure
+				}
 			}
-		}
-		if len(jsonDirName) == 0 {
-			log.Errorf("First Argument have to be JSON directory name : %s", err)
-			return subcommands.ExitFailure
-		}
-	} else {
-		stat, _ := os.Stdin.Stat()
-		if (stat.Mode() & os.ModeCharDevice) == 0 {
-			bytes, err := ioutil.ReadAll(os.Stdin)
+
+			filled, err := fillCveInfoFromCveDB(r)
 			if err != nil {
-				log.Errorf("Failed to read stdin: %s", err)
+				log.Errorf("Failed to fill CVE information: %s", err)
 				return subcommands.ExitFailure
 			}
-			fields := strings.Fields(string(bytes))
-			if 0 < len(fields) {
-				jsonDirName = fields[0]
+
+			if err := overwriteJSONFile(jsonDir, filled); err != nil {
+				log.Errorf("Failed to write JSON: %s", err)
+				return subcommands.ExitFailure
 			}
+			results = append(results, filled)
+		} else {
+			results = append(results, r)
 		}
 	}
-	return report.RunTui(jsonDirName)
+	history.ScanResults = results
+	return report.RunTui(history)
 }
diff --git a/commands/util.go b/commands/util.go
new file mode 100644
index 00000000..6a7f3451
--- /dev/null
+++ b/commands/util.go
@@ -0,0 +1,225 @@
+/* Vuls - Vulnerability Scanner
+Copyright (C) 2016  Future Architect, Inc. Japan.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see .
+*/
+
+package commands
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"regexp"
+	"sort"
+	"strings"
+
+	c "github.com/future-architect/vuls/config"
+	"github.com/future-architect/vuls/cveapi"
+	"github.com/future-architect/vuls/models"
+	"github.com/future-architect/vuls/report"
+	"github.com/future-architect/vuls/util"
+)
+
+// jsonDirPattern is file name pattern of JSON directory
+// 2016-11-16T10:43:28+09:00
+// 2016-11-16T10:43:28Z
+var jsonDirPattern = regexp.MustCompile(
+	`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:Z|[+-]\d{2}:\d{2})$`)
+
+// JSONDirs is array of json files path.
+type jsonDirs []string
+
+// sort as recent directories are at the head
+func (d jsonDirs) Len() int {
+	return len(d)
+}
+func (d jsonDirs) Swap(i, j int) {
+	d[i], d[j] = d[j], d[i]
+}
+func (d jsonDirs) Less(i, j int) bool {
+	return d[j] < d[i]
+}
+
+// getValidJSONDirs return valid json directory as array
+// Returned array is sorted so that recent directories are at the head
+func lsValidJSONDirs() (dirs jsonDirs, err error) {
+	var dirInfo []os.FileInfo
+	if dirInfo, err = ioutil.ReadDir(c.Conf.ResultsDir); err != nil {
+		err = fmt.Errorf("Failed to read %s: %s", c.Conf.ResultsDir, err)
+		return
+	}
+	for _, d := range dirInfo {
+		if d.IsDir() && jsonDirPattern.MatchString(d.Name()) {
+			jsonDir := filepath.Join(c.Conf.ResultsDir, d.Name())
+			dirs = append(dirs, jsonDir)
+		}
+	}
+	sort.Sort(dirs)
+	return
+}
+
+// jsonDir returns
+// If there is an arg, check if it is a valid format and return the corresponding path under results.
+// If passed via PIPE (such as history subcommand), return that path.
+// Otherwise, returns the path of the latest directory
+func jsonDir(args []string) (string, error) {
+	var err error
+	var dirs jsonDirs
+
+	if 0 < len(args) {
+		if dirs, err = lsValidJSONDirs(); err != nil {
+			return "", err
+		}
+
+		path := filepath.Join(c.Conf.ResultsDir, args[0])
+		for _, d := range dirs {
+			ss := strings.Split(d, string(os.PathSeparator))
+			timedir := ss[len(ss)-1]
+			if timedir == args[0] {
+				return path, nil
+			}
+		}
+
+		return "", fmt.Errorf("Invalid path: %s", path)
+	}
+
+	// PIPE
+	stat, _ := os.Stdin.Stat()
+	if (stat.Mode() & os.ModeCharDevice) == 0 {
+		bytes, err := ioutil.ReadAll(os.Stdin)
+		if err != nil {
+			return "", fmt.Errorf("Failed to read stdin: %s", err)
+		}
+		fields := strings.Fields(string(bytes))
+		if 0 < len(fields) {
+			return filepath.Join(c.Conf.ResultsDir, fields[0]), nil
+		}
+		return "", fmt.Errorf("Stdin is invalid: %s", string(bytes))
+	}
+
+	// returns latest dir when no args or no PIPE
+	if dirs, err = lsValidJSONDirs(); err != nil {
+		return "", err
+	}
+	if len(dirs) == 0 {
+		return "", fmt.Errorf("No results under %s",
+			c.Conf.ResultsDir)
+	}
+	return dirs[0], nil
+}
+
+// loadOneScanHistory read JSON data
+func loadOneScanHistory(jsonDir string) (scanHistory models.ScanHistory, err error) {
+	var results []models.ScanResult
+	var files []os.FileInfo
+	if files, err = ioutil.ReadDir(jsonDir); err != nil {
+		err = fmt.Errorf("Failed to read %s: %s", jsonDir, err)
+		return
+	}
+	for _, f := range files {
+		if filepath.Ext(f.Name()) != ".json" {
+			continue
+		}
+		var r models.ScanResult
+		var data []byte
+		path := filepath.Join(jsonDir, f.Name())
+		if data, err = ioutil.ReadFile(path); err != nil {
+			err = fmt.Errorf("Failed to read %s: %s", path, err)
+			return
+		}
+		if json.Unmarshal(data, &r) != nil {
+			err = fmt.Errorf("Failed to parse %s: %s", path, err)
+			return
+		}
+		results = append(results, r)
+	}
+	if len(results) == 0 {
+		err = fmt.Errorf("There is no json file under %s", jsonDir)
+		return
+	}
+
+	scanHistory = models.ScanHistory{
+		ScanResults: results,
+	}
+	return
+}
+
+func fillCveInfoFromCveDB(r models.ScanResult) (filled models.ScanResult, err error) {
+	sInfo := c.Conf.Servers[r.ServerName]
+	vs, err := scanVulnByCpeNames(sInfo.CpeNames, r.ScannedCves)
+	if err != nil {
+		return
+	}
+	r.ScannedCves = vs
+	filled, err = r.FillCveDetail()
+	if err != nil {
+		return
+	}
+	return
+}
+
+func overwriteJSONFile(dir string, r models.ScanResult) error {
+	before := c.Conf.FormatJSON
+	c.Conf.FormatJSON = true
+	w := report.LocalFileWriter{CurrentDir: dir}
+	if err := w.Write(r); err != nil {
+		return fmt.Errorf("Failed to write summary report: %s", err)
+	}
+	c.Conf.FormatJSON = before
+	return nil
+}
+
+func scanVulnByCpeNames(cpeNames []string, scannedVulns []models.VulnInfo) ([]models.VulnInfo,
+	error) {
+	// To remove duplicate
+	set := map[string]models.VulnInfo{}
+	for _, v := range scannedVulns {
+		set[v.CveID] = v
+	}
+
+	for _, name := range cpeNames {
+		details, err := cveapi.CveClient.FetchCveDetailsByCpeName(name)
+		if err != nil {
+			return nil, err
+		}
+		for _, detail := range details {
+			if val, ok := set[detail.CveID]; ok {
+				names := val.CpeNames
+				names = util.AppendIfMissing(names, name)
+				val.CpeNames = names
+				set[detail.CveID] = val
+			} else {
+				set[detail.CveID] = models.VulnInfo{
+					CveID:    detail.CveID,
+					CpeNames: []string{name},
+				}
+			}
+		}
+	}
+
+	vinfos := []models.VulnInfo{}
+	for key := range set {
+		vinfos = append(vinfos, set[key])
+	}
+	return vinfos, nil
+}
+
+func needToRefreshCve(r models.ScanResult) bool {
+	return r.Lang != c.Conf.Lang || len(r.KnownCves) == 0 &&
+		len(r.UnknownCves) == 0 &&
+		len(r.IgnoredCves) == 0
+}
diff --git a/config/config.go b/config/config.go
index c21c6d22..f50d861c 100644
--- a/config/config.go
+++ b/config/config.go
@@ -35,7 +35,7 @@ type Config struct {
 	DebugSQL bool
 	Lang     string
 
-	Mail    smtpConf
+	EMail   smtpConf
 	Slack   SlackConf
 	Default ServerInfo
 	Servers map[string]ServerInfo
@@ -56,6 +56,14 @@ type Config struct {
 	CveDBPath   string
 	CacheDBPath string
 
+	FormatXML         bool
+	FormatJSON        bool
+	FormatOneLineText bool
+	FormatShortText   bool
+	FormatFullText    bool
+
+	GZIP bool
+
 	AwsProfile string
 	AwsRegion  string
 	S3Bucket   string
@@ -63,19 +71,48 @@ type Config struct {
 	AzureAccount   string
 	AzureKey       string
 	AzureContainer string
-
-	//  CpeNames      []string
-	//  SummaryMode          bool
 }
 
-// Validate configuration
-func (c Config) Validate() bool {
+// ValidateOnConfigtest validates
+func (c Config) ValidateOnConfigtest() bool {
 	errs := []error{}
 
 	if runtime.GOOS == "windows" && c.SSHExternal {
 		errs = append(errs, fmt.Errorf("-ssh-external cannot be used on windows"))
 	}
 
+	_, err := valid.ValidateStruct(c)
+	if err != nil {
+		errs = append(errs, err)
+	}
+
+	for _, err := range errs {
+		log.Error(err)
+	}
+
+	return len(errs) == 0
+}
+
+// ValidateOnPrepare validates configuration
+func (c Config) ValidateOnPrepare() bool {
+	return c.ValidateOnConfigtest()
+}
+
+// ValidateOnScan validates configuration
+func (c Config) ValidateOnScan() bool {
+	errs := []error{}
+
+	if len(c.ResultsDir) != 0 {
+		if ok, _ := valid.IsFilePath(c.ResultsDir); !ok {
+			errs = append(errs, fmt.Errorf(
+				"JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
+		}
+	}
+
+	if runtime.GOOS == "windows" && c.SSHExternal {
+		errs = append(errs, fmt.Errorf("-ssh-external cannot be used on windows"))
+	}
+
 	if len(c.ResultsDir) != 0 {
 		if ok, _ := valid.IsFilePath(c.ResultsDir); !ok {
 			errs = append(errs, fmt.Errorf(
@@ -83,25 +120,6 @@ func (c Config) Validate() bool {
 		}
 	}
 
-	// If no valid DB type is set, default to sqlite3
-	if c.CveDBType == "" {
-		c.CveDBType = "sqlite3"
-	}
-
-	if c.CveDBType != "sqlite3" && c.CveDBType != "mysql" {
-		errs = append(errs, fmt.Errorf(
-			"CVE DB type must be either 'sqlite3' or 'mysql'.  -cve-dictionary-dbtype: %s", c.CveDBType))
-	}
-
-	if c.CveDBType == "sqlite3" {
-		if len(c.CveDBPath) != 0 {
-			if ok, _ := valid.IsFilePath(c.CveDBPath); !ok {
-				errs = append(errs, fmt.Errorf(
-					"SQLite3 DB(Cve Dictionary) path must be a *Absolute* file path. -cve-dictionary-dbpath: %s", c.CveDBPath))
-			}
-		}
-	}
-
 	if len(c.CacheDBPath) != 0 {
 		if ok, _ := valid.IsFilePath(c.CacheDBPath); !ok {
 			errs = append(errs, fmt.Errorf(
@@ -114,7 +132,42 @@ func (c Config) Validate() bool {
 		errs = append(errs, err)
 	}
 
-	if mailerrs := c.Mail.Validate(); 0 < len(mailerrs) {
+	for _, err := range errs {
+		log.Error(err)
+	}
+
+	return len(errs) == 0
+}
+
+// ValidateOnReport validates configuration
+func (c Config) ValidateOnReport() bool {
+	errs := []error{}
+
+	if len(c.ResultsDir) != 0 {
+		if ok, _ := valid.IsFilePath(c.ResultsDir); !ok {
+			errs = append(errs, fmt.Errorf(
+				"JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
+		}
+	}
+
+	if c.CveDBType != "sqlite3" && c.CveDBType != "mysql" {
+		errs = append(errs, fmt.Errorf(
+			"CVE DB type must be either 'sqlite3' or 'mysql'.  -cve-dictionary-dbtype: %s", c.CveDBType))
+	}
+
+	if c.CveDBType == "sqlite3" {
+		if ok, _ := valid.IsFilePath(c.CveDBPath); !ok {
+			errs = append(errs, fmt.Errorf(
+				"SQLite3 DB(CVE-Dictionary) path must be a *Absolute* file path. -cve-dictionary-dbpath: %s", c.CveDBPath))
+		}
+	}
+
+	_, err := valid.ValidateStruct(c)
+	if err != nil {
+		errs = append(errs, err)
+	}
+
+	if mailerrs := c.EMail.Validate(); 0 < len(mailerrs) {
 		errs = append(errs, mailerrs...)
 	}
 
@@ -129,6 +182,36 @@ func (c Config) Validate() bool {
 	return len(errs) == 0
 }
 
+// ValidateOnTui validates configuration
+func (c Config) ValidateOnTui() bool {
+	errs := []error{}
+
+	if len(c.ResultsDir) != 0 {
+		if ok, _ := valid.IsFilePath(c.ResultsDir); !ok {
+			errs = append(errs, fmt.Errorf(
+				"JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
+		}
+	}
+
+	if c.CveDBType != "sqlite3" && c.CveDBType != "mysql" {
+		errs = append(errs, fmt.Errorf(
+			"CVE DB type must be either 'sqlite3' or 'mysql'.  -cve-dictionary-dbtype: %s", c.CveDBType))
+	}
+
+	if c.CveDBType == "sqlite3" {
+		if ok, _ := valid.IsFilePath(c.CveDBPath); !ok {
+			errs = append(errs, fmt.Errorf(
+				"SQLite3 DB(CVE-Dictionary) path must be a *Absolute* file path. -cve-dictionary-dbpath: %s", c.CveDBPath))
+		}
+	}
+
+	for _, err := range errs {
+		log.Error(err)
+	}
+
+	return len(errs) == 0
+}
+
 // smtpConf is smtp config
 type smtpConf struct {
 	SMTPAddr string
diff --git a/config/tomlloader.go b/config/tomlloader.go
index abb7a9ed..f4c4f6af 100644
--- a/config/tomlloader.go
+++ b/config/tomlloader.go
@@ -43,7 +43,7 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) error {
 		return err
 	}
 
-	Conf.Mail = conf.Mail
+	Conf.EMail = conf.EMail
 	Conf.Slack = conf.Slack
 
 	d := conf.Default
@@ -119,7 +119,7 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) error {
 				return fmt.Errorf(
 					"Failed to read OWASP Dependency Check XML: %s", err)
 			}
-			log.Infof("Loaded from OWASP Dependency Check XML: %s",
+			log.Debugf("Loaded from OWASP Dependency Check XML: %s",
 				s.ServerName)
 			s.CpeNames = append(s.CpeNames, cpes...)
 		}
diff --git a/cveapi/cve_client.go b/cveapi/cve_client.go
index 44f5f282..4560c43a 100644
--- a/cveapi/cve_client.go
+++ b/cveapi/cve_client.go
@@ -48,7 +48,7 @@ func (api *cvedictClient) initialize() {
 }
 
 func (api cvedictClient) CheckHealth() (ok bool, err error) {
-	if config.Conf.CveDBPath != "" {
+	if config.Conf.CveDictionaryURL == "" {
 		log.Debugf("get cve-dictionary from %s", config.Conf.CveDBType)
 		return true, nil
 	}
@@ -71,7 +71,7 @@ type response struct {
 }
 
 func (api cvedictClient) FetchCveDetails(cveIDs []string) (cveDetails cve.CveDetails, err error) {
-	if config.Conf.CveDBPath != "" {
+	if config.Conf.CveDictionaryURL == "" {
 		return api.FetchCveDetailsFromCveDB(cveIDs)
 	}
 
@@ -129,7 +129,6 @@ func (api cvedictClient) FetchCveDetails(cveIDs []string) (cveDetails cve.CveDet
 			fmt.Errorf("Failed to fetch CVE. err: %v", errs)
 	}
 
-	// order by CVE ID desc
 	sort.Sort(cveDetails)
 	return
 }
@@ -137,7 +136,11 @@ func (api cvedictClient) FetchCveDetails(cveIDs []string) (cveDetails cve.CveDet
 func (api cvedictClient) FetchCveDetailsFromCveDB(cveIDs []string) (cveDetails cve.CveDetails, err error) {
 	log.Debugf("open cve-dictionary db (%s)", config.Conf.CveDBType)
 	cveconfig.Conf.DBType = config.Conf.CveDBType
-	cveconfig.Conf.DBPath = config.Conf.CveDBPath
+	if config.Conf.CveDBType == "sqlite3" {
+		cveconfig.Conf.DBPath = config.Conf.CveDBPath
+	} else {
+		cveconfig.Conf.DBPath = config.Conf.CveDictionaryURL
+	}
 	cveconfig.Conf.DebugSQL = config.Conf.DebugSQL
 	if err := cvedb.OpenDB(); err != nil {
 		return []cve.CveDetail{},
@@ -194,7 +197,7 @@ type responseGetCveDetailByCpeName struct {
 }
 
 func (api cvedictClient) FetchCveDetailsByCpeName(cpeName string) ([]cve.CveDetail, error) {
-	if config.Conf.CveDBPath != "" {
+	if config.Conf.CveDictionaryURL == "" {
 		return api.FetchCveDetailsByCpeNameFromDB(cpeName)
 	}
 
diff --git a/glide.lock b/glide.lock
index ccbff0ca..3f2cee6e 100644
--- a/glide.lock
+++ b/glide.lock
@@ -1,5 +1,5 @@
-hash: ca64aef6e9e94c7be91f79b88edb847363c8a5bd48da4ad27784e9342c8db6e2
-updated: 2016-11-14T17:14:19.692072231Z
+hash: c3167d83e68562cd7ef73f138ce60cb9e60b72b50394e8615388d1f3ba9fbef2
+updated: 2017-01-02T09:37:09.437363123+09:00
 imports:
 - name: github.com/asaskevich/govalidator
   version: 7b3beb6df3c42abd3509abfc3bcacc0fbfb7c877
@@ -46,7 +46,7 @@ imports:
 - name: github.com/go-ini/ini
   version: 6e4869b434bd001f6983749881c7ead3545887d8
 - name: github.com/go-sql-driver/mysql
-  version: 2a6c6079c7eff49a7e9d641e109d922f124a3e4c
+  version: d512f204a577a4ab037a1816604c48c9c13210be
 - name: github.com/google/subcommands
   version: a71b91e238406bd68766ee52db63bebedce0e9f6
 - name: github.com/gosuri/uitable
@@ -60,6 +60,7 @@ imports:
   version: 39165d498058a823126af3cbf4d2a3b0e1acf11e
   subpackages:
   - dialects/mysql
+  - dialects/sqlite
 - name: github.com/jinzhu/inflection
   version: 74387dc39a75e970e7a3ae6a3386b5bd2e5c5cff
 - name: github.com/jmespath/go-jmespath
@@ -69,7 +70,7 @@ imports:
 - name: github.com/k0kubun/pp
   version: f5dce6ed0ccf6c350f1679964ff6b61f3d6d2033
 - name: github.com/kotakanbe/go-cve-dictionary
-  version: 2dd369d26145eab2178f6d509821fcabc9391627
+  version: 7eb1f1a2e7e436177570bf234e21c2ed9489d3fb
   subpackages:
   - config
   - db
@@ -89,7 +90,7 @@ imports:
 - name: github.com/mattn/go-runewidth
   version: 737072b4e32b7a5018b4a7125da8d12de90e8045
 - name: github.com/mattn/go-sqlite3
-  version: e5a3c16c5c1d80b24f633e68aecd6b0702786d3d
+  version: 5510da399572b4962c020184bb291120c0a412e2
 - name: github.com/mgutz/ansi
   version: c286dcecd19ff979eeb73ea444e479b903f2cfcb
 - name: github.com/moul/http2curl
@@ -112,9 +113,8 @@ imports:
   - ssh/agent
   - ssh/terminal
 - name: golang.org/x/net
-  version: cf4effbb9db1f3ef07f7e1891402991b6afbb276
+  version: 1d7a0b2100da090d8b02afcfb42f97e2c77e71a4
   subpackages:
-  - context
   - publicsuffix
 - name: golang.org/x/sys
   version: 9bb9f0998d48b31547d975974935ae9b48c7a03c
diff --git a/glide.yaml b/glide.yaml
index 736d995a..ce42b29f 100644
--- a/glide.yaml
+++ b/glide.yaml
@@ -15,16 +15,15 @@ import:
 - package: github.com/boltdb/bolt
 - package: github.com/cenkalti/backoff
 - package: github.com/google/subcommands
-  branch: context
 - package: github.com/gosuri/uitable
 - package: github.com/howeyc/gopass
-- package: github.com/jinzhu/gorm
 - package: github.com/jroimartin/gocui
 - package: github.com/k0kubun/pp
 - package: github.com/kotakanbe/go-cve-dictionary
   subpackages:
   - config
   - db
+  - log
   - models
 - package: github.com/kotakanbe/go-pingscanner
 - package: github.com/kotakanbe/logrus-prefixed-formatter
@@ -35,6 +34,3 @@ import:
   subpackages:
   - ssh
   - ssh/agent
-- package: golang.org/x/net
-  subpackages:
-  - context
diff --git a/img/vuls-architecture.graphml b/img/vuls-architecture.graphml
index 6b6f7237..0639fa6c 100644
--- a/img/vuls-architecture.graphml
+++ b/img/vuls-architecture.graphml
@@ -37,7 +37,7 @@
         
           
             
-              
+              
               
               
               Vulnerbility Database
@@ -63,7 +63,7 @@
         
           
             
-              
+              
               
               
               JVN
@@ -81,7 +81,7 @@
         
           
             
-              
+              
               
               
               NVD
@@ -103,7 +103,7 @@
         
           
             
-              
+              
               
               
               Distribution Support
@@ -129,7 +129,7 @@
         
           
             
-              
+              
               
               
               apptitude
@@ -147,7 +147,7 @@ changelog
         
           
             
-              
+              
               
               
               yum
@@ -165,7 +165,7 @@ changelog
         
           
             
-              
+              
               
               
               RHSA (RedHat)
@@ -183,7 +183,7 @@ ALAS (Amazon)
         
           
             
-              
+              
               
               
               FreeBSD Support
@@ -202,7 +202,7 @@ ALAS (Amazon)
     
       
         
-          
+          
           
           
           
@@ -222,14 +222,14 @@ ALAS (Amazon)
         
           
             
-              
+              
               
               
-              Vuls
+              Vuls
               
               
               
-              
+              
             
             
               
@@ -248,10 +248,10 @@ ALAS (Amazon)
         
           
             
-              
-              
+              
+              
               
-              Report
+              Scan
                   
                 
                 
@@ -265,10 +265,10 @@ ALAS (Amazon)
         
           
             
-              
-              
+              
+              
               
-              TUI View
+              Report
                   
                 
                 
@@ -282,10 +282,11 @@ ALAS (Amazon)
         
           
             
-              
+              
               
               
-              Scan
+              VulsRepo
+(WebUI)
                   
                 
                 
@@ -299,11 +300,10 @@ ALAS (Amazon)
         
           
             
-              
-              
+              
+              
               
-              Web View
-(Vulsrepo)
+              TUI
                   
                 
                 
@@ -319,7 +319,7 @@ ALAS (Amazon)
     
       
         
-          
+          
           
           
           System Operator
@@ -339,7 +339,7 @@ ALAS (Amazon)
     
       
         
-          
+          
           
           
           
@@ -353,7 +353,7 @@ ALAS (Amazon)
     
       
         
-          
+          
           
           
           
@@ -468,14 +468,14 @@ ALAS (Amazon)
         
           
             
-              
+              
               
               
-              Docker Containers
+              Docker Containers
               
               
               
-              
+              
             
             
               
@@ -534,14 +534,14 @@ Container
         
           
             
-              
+              
               
               
-              Linux/FreeBSD Servers
+              Linux/FreeBSD
               
               
               
-              
+              
             
             
               
@@ -599,7 +599,7 @@ Container
         
           
             
-              
+              
               
               
               results dir
@@ -625,7 +625,7 @@ Container
         
           
             
-              
+              
               
               
               JSON
@@ -642,7 +642,7 @@ Container
         
           
             
-              
+              
               
               
               JSON
@@ -659,7 +659,7 @@ Container
         
           
             
-              
+              
               
               
               JSON
@@ -675,6 +675,104 @@ Container
         
       
     
+    
+      
+      
+        
+          
+          
+          
+          
+          
+        
+      
+    
+    
+      
+        
+          
+          
+          
+          Azure
+BLOB
+              
+            
+            
+              
+            
+          
+          
+        
+      
+    
+    
+      
+      
+        
+          
+          
+          
+          .xml
+              
+            
+            
+              
+            
+          
+        
+      
+    
+    
+      
+      
+        
+          
+          
+          
+          .txt
+              
+            
+            
+              
+            
+          
+        
+      
+    
+    
+      
+      
+        
+          
+          
+          
+          .json
+              
+            
+            
+              
+            
+          
+        
+      
+    
+    
+      
+      
+        
+          
+          
+          
+          .gz
+              
+            
+            
+              
+            
+          
+        
+      
+    
     
       
         
@@ -700,7 +798,7 @@ Vulnerability data
           
           
           
-          HTTP
+          HTTP
               
             
             
@@ -712,31 +810,13 @@ Vulnerability data
         
       
     
-    
+    
       
         
           
           
           
-          HTTP or --cve-dictoianry-dbpath option
-              
-            
-            
-              
-            
-            
-          
-          
-        
-      
-    
-    
-      
-        
-          
-          
-          
-          HTTP
+          HTTP
               
             
             
@@ -748,17 +828,17 @@ Vulnerability data
         
       
     
-    
+    
       
         
-          
+          
           
           
-          send
+          WebUI
               
             
             
-              
+              
             
             
           
@@ -766,49 +846,13 @@ Vulnerability data
         
       
     
-    
+    
       
         
-          
+          
           
           
-          Generate
-              
-            
-            
-              
-            
-            
-          
-          
-        
-      
-    
-    
-      
-        
-          
-          
-          
-          View Detail Information
-              
-            
-            
-              
-            
-            
-          
-          
-        
-      
-    
-    
-      
-        
-          
-          
-          
-          SSH
+          SSH
               
             
             
@@ -820,17 +864,17 @@ Vulnerability data
         
       
     
-    
+    
       
         
           
           
           
-          SSH
+          SSH
               
             
             
-              
+              
             
             
           
@@ -856,7 +900,7 @@ Vulnerability data
         
       
     
-    
+    
       
         
           
@@ -866,7 +910,7 @@ Vulnerability data
         
       
     
-    
+    
       
         
           
@@ -894,46 +938,17 @@ Vulnerability data
         
       
     
-    
+    
       
         
           
           
           
-          
-        
-      
-    
-    
-      
-        
-          
-          
-          
-          Notify
+          Notify
               
             
             
-              
-            
-            
-          
-          
-        
-      
-    
-    
-      
-        
-          
-          
-          
-          
-            
-              
-            
-            
-              
+              
             
             
           
@@ -959,7 +974,7 @@ Vulnerability data
         
       
     
-    
+    
       
         
           
@@ -969,17 +984,7 @@ Vulnerability data
         
       
     
-    
-      
-        
-          
-          
-          
-          
-        
-      
-    
-    
+    
       
       
         
@@ -990,7 +995,7 @@ Vulnerability data
         
       
     
-    
+    
       
       
         
@@ -1001,6 +1006,100 @@ Vulnerability data
         
       
     
+    
+      
+      
+        
+          
+          
+          
+          
+        
+      
+    
+    
+      
+      
+        
+          
+          
+          
+          
+        
+      
+    
+    
+      
+      
+        
+          
+          
+          
+          
+        
+      
+    
+    
+      
+      
+        
+          
+          
+          
+          Put
+              
+            
+            
+              
+            
+            
+          
+          
+        
+      
+    
+    
+      
+      
+        
+          
+          
+          
+          
+        
+      
+    
+    
+      
+      
+        
+          
+          
+          
+          
+        
+      
+    
+    
+      
+      
+        
+          
+          
+          
+               View Results
+ on Terminal
+              
+            
+            
+              
+            
+            
+          
+          
+        
+      
+    
   
   
     
@@ -1185,6 +1284,180 @@ Vulnerability data
        style="fill:#373d47;fill-opacity:1;fill-rule:nonzero;stroke:none"
        id="path46-3"
        inkscape:connector-curvature="0" /></g></svg>
+      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
     
   
 
diff --git a/img/vuls-architecture.png b/img/vuls-architecture.png
index eeeb7d2e..08a81457 100644
Binary files a/img/vuls-architecture.png and b/img/vuls-architecture.png differ
diff --git a/img/vuls-scan-flow.png b/img/vuls-scan-flow.png
index a82b9403..f4959b35 100644
Binary files a/img/vuls-scan-flow.png and b/img/vuls-scan-flow.png differ
diff --git a/main.go b/main.go
index 8fef988c..cad1336e 100644
--- a/main.go
+++ b/main.go
@@ -31,7 +31,7 @@ import (
 )
 
 // Version of Vuls
-var version = "0.1.7"
+var version = "0.2.0"
 
 // Revision of Git
 var revision string
@@ -45,6 +45,7 @@ func main() {
 	subcommands.Register(&commands.ScanCmd{}, "scan")
 	subcommands.Register(&commands.PrepareCmd{}, "prepare")
 	subcommands.Register(&commands.HistoryCmd{}, "history")
+	subcommands.Register(&commands.ReportCmd{}, "report")
 	subcommands.Register(&commands.ConfigtestCmd{}, "configtest")
 
 	var v = flag.Bool("v", false, "Show version")
diff --git a/models/models.go b/models/models.go
index d5e75bf0..1ad99768 100644
--- a/models/models.go
+++ b/models/models.go
@@ -23,15 +23,13 @@ import (
 	"time"
 
 	"github.com/future-architect/vuls/config"
-	"github.com/jinzhu/gorm"
+	"github.com/future-architect/vuls/cveapi"
 	cve "github.com/kotakanbe/go-cve-dictionary/models"
 )
 
 // ScanHistory is the history of Scanning.
 type ScanHistory struct {
-	gorm.Model
 	ScanResults ScanResults
-	ScannedAt   time.Time
 }
 
 // ScanResults is slice of ScanResult.
@@ -55,43 +53,111 @@ func (s ScanResults) Less(i, j int) bool {
 	return s[i].ServerName < s[j].ServerName
 }
 
-// FilterByCvssOver is filter function.
-func (s ScanResults) FilterByCvssOver() (filtered ScanResults) {
-	for _, result := range s {
-		cveInfos := []CveInfo{}
-		for _, cveInfo := range result.KnownCves {
-			if config.Conf.CvssScoreOver < cveInfo.CveDetail.CvssScore(config.Conf.Lang) {
-				cveInfos = append(cveInfos, cveInfo)
-			}
-		}
-		result.KnownCves = cveInfos
-		filtered = append(filtered, result)
-	}
-	return
-}
-
 // ScanResult has the result of scanned CVE information.
 type ScanResult struct {
-	gorm.Model    `json:"-" xml:"-"`
-	ScanHistoryID uint `json:"-" xml:"-"`
-	ScannedAt     time.Time
+	ScannedAt time.Time
 
+	Lang       string
 	ServerName string // TOML Section key
-	//  Hostname    string
-	Family  string
-	Release string
+	Family     string
+	Release    string
+	Container  Container
+	Platform   Platform
 
-	Container Container
+	// Scanned Vulns via SSH + CPE Vulns
+	ScannedCves []VulnInfo
 
-	Platform Platform
-
-	//  Fqdn        string
-	//  NWLinks     []NWLink
 	KnownCves   []CveInfo
 	UnknownCves []CveInfo
 	IgnoredCves []CveInfo
 
-	Optional [][]interface{} `gorm:"-"`
+	Packages PackageInfoList
+
+	Optional [][]interface{}
+}
+
+// FillCveDetail fetches CVE detailed information from
+// CVE Database, and then set to fields.
+func (r ScanResult) FillCveDetail() (ScanResult, error) {
+	set := map[string]VulnInfo{}
+	var cveIDs []string
+	for _, v := range r.ScannedCves {
+		set[v.CveID] = v
+		cveIDs = append(cveIDs, v.CveID)
+	}
+
+	ds, err := cveapi.CveClient.FetchCveDetails(cveIDs)
+	if err != nil {
+		return r, err
+	}
+
+	icves := config.Conf.Servers[r.ServerName].IgnoreCves
+
+	var known, unknown, ignored CveInfos
+	for _, d := range ds {
+		cinfo := CveInfo{
+			CveDetail: d,
+			VulnInfo:  set[d.CveID],
+		}
+
+		// ignored
+		found := false
+		for _, icve := range icves {
+			if icve == d.CveID {
+				ignored = append(ignored, cinfo)
+				found = true
+				break
+			}
+		}
+		if found {
+			continue
+		}
+
+		// unknown
+		if d.CvssScore(config.Conf.Lang) <= 0 {
+			unknown = append(unknown, cinfo)
+			continue
+		}
+
+		// known
+		known = append(known, cinfo)
+	}
+	sort.Sort(known)
+	sort.Sort(unknown)
+	sort.Sort(ignored)
+	r.KnownCves = known
+	r.UnknownCves = unknown
+	r.IgnoredCves = ignored
+	return r, nil
+}
+
+// FilterByCvssOver is filter function.
+func (r ScanResult) FilterByCvssOver() ScanResult {
+	cveInfos := []CveInfo{}
+	for _, cveInfo := range r.KnownCves {
+		if config.Conf.CvssScoreOver < cveInfo.CveDetail.CvssScore(config.Conf.Lang) {
+			cveInfos = append(cveInfos, cveInfo)
+		}
+	}
+	r.KnownCves = cveInfos
+	return r
+}
+
+// ReportFileName returns the filename on localhost without extention
+func (r ScanResult) ReportFileName() (name string) {
+	if len(r.Container.ContainerID) == 0 {
+		return fmt.Sprintf("%s", r.ServerName)
+	}
+	return fmt.Sprintf("%s@%s", r.Container.Name, r.ServerName)
+}
+
+// ReportKeyName returns the name of key on S3, Azure-Blob without extention
+func (r ScanResult) ReportKeyName() (name string) {
+	timestr := r.ScannedAt.Format(time.RFC3339)
+	if len(r.Container.ContainerID) == 0 {
+		return fmt.Sprintf("%s/%s", timestr, r.ServerName)
+	}
+	return fmt.Sprintf("%s/%s@%s", timestr, r.Container.Name, r.ServerName)
 }
 
 // ServerInfo returns server name one line
@@ -141,15 +207,15 @@ func (r ScanResult) ServerInfoTui() string {
 
 // CveSummary summarize the number of CVEs group by CVSSv2 Severity
 func (r ScanResult) CveSummary() string {
-	var high, middle, low, unknown int
+	var high, medium, low, unknown int
 	cves := append(r.KnownCves, r.UnknownCves...)
 	for _, cveInfo := range cves {
 		score := cveInfo.CveDetail.CvssScore(config.Conf.Lang)
 		switch {
-		case 7.0 < score:
+		case 7.0 <= score:
 			high++
-		case 4.0 < score:
-			middle++
+		case 4.0 <= score:
+			medium++
 		case 0 < score:
 			low++
 		default:
@@ -158,11 +224,11 @@ func (r ScanResult) CveSummary() string {
 	}
 
 	if config.Conf.IgnoreUnscoredCves {
-		return fmt.Sprintf("Total: %d (High:%d Middle:%d Low:%d)",
-			high+middle+low, high, middle, low)
+		return fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d)",
+			high+medium+low, high, medium, low)
 	}
-	return fmt.Sprintf("Total: %d (High:%d Middle:%d Low:%d ?:%d)",
-		high+middle+low+unknown, high, middle, low, unknown)
+	return fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d ?:%d)",
+		high+medium+low+unknown, high, medium, low, unknown)
 }
 
 // AllCves returns Known and Unknown CVEs
@@ -172,15 +238,59 @@ func (r ScanResult) AllCves() []CveInfo {
 
 // NWLink has network link information.
 type NWLink struct {
-	gorm.Model   `json:"-" xml:"-"`
-	ScanResultID uint `json:"-" xml:"-"`
-
 	IPAddress string
 	Netmask   string
 	DevName   string
 	LinkState string
 }
 
+// VulnInfos is VulnInfo list, getter/setter, sortable methods.
+type VulnInfos []VulnInfo
+
+// VulnInfo holds a vulnerability information and unsecure packages
+type VulnInfo struct {
+	CveID            string
+	Packages         PackageInfoList
+	DistroAdvisories []DistroAdvisory // for Aamazon, RHEL, FreeBSD
+	CpeNames         []string
+}
+
+// FindByCveID find by CVEID
+func (s VulnInfos) FindByCveID(cveID string) (VulnInfo, bool) {
+	for _, p := range s {
+		if cveID == p.CveID {
+			return p, true
+		}
+	}
+	return VulnInfo{CveID: cveID}, false
+}
+
+// immutable
+func (s VulnInfos) set(cveID string, v VulnInfo) VulnInfos {
+	for i, p := range s {
+		if cveID == p.CveID {
+			s[i] = v
+			return s
+		}
+	}
+	return append(s, v)
+}
+
+// Len implement Sort Interface
+func (s VulnInfos) Len() int {
+	return len(s)
+}
+
+// Swap implement Sort Interface
+func (s VulnInfos) Swap(i, j int) {
+	s[i], s[j] = s[j], s[i]
+}
+
+// Less implement Sort Interface
+func (s VulnInfos) Less(i, j int) bool {
+	return s[i].CveID < s[j].CveID
+}
+
 // CveInfos is for sorting
 type CveInfos []CveInfo
 
@@ -202,21 +312,8 @@ func (c CveInfos) Less(i, j int) bool {
 
 // CveInfo has Cve Information.
 type CveInfo struct {
-	gorm.Model   `json:"-" xml:"-"`
-	ScanResultID uint `json:"-" xml:"-"`
-
-	CveDetail        cve.CveDetail
-	Packages         []PackageInfo
-	DistroAdvisories []DistroAdvisory
-	CpeNames         []CpeName
-}
-
-// CpeName has CPE name
-type CpeName struct {
-	gorm.Model `json:"-" xml:"-"`
-	CveInfoID  uint `json:"-" xml:"-"`
-
-	Name string
+	CveDetail cve.CveDetail
+	VulnInfo
 }
 
 // PackageInfoList is slice of PackageInfo
@@ -260,6 +357,34 @@ func (ps PackageInfoList) FindByName(name string) (result PackageInfo, found boo
 	return PackageInfo{}, false
 }
 
+// MergeNewVersion merges candidate version information to the receiver struct
+func (ps PackageInfoList) MergeNewVersion(as PackageInfoList) {
+	for _, a := range as {
+		for i, p := range ps {
+			if p.Name == a.Name {
+				ps[i].NewVersion = a.NewVersion
+				ps[i].NewRelease = a.NewRelease
+			}
+		}
+	}
+}
+
+func (ps PackageInfoList) countUpdatablePacks() int {
+	count := 0
+	for _, p := range ps {
+		if len(p.NewVersion) != 0 {
+			count++
+		}
+	}
+	return count
+}
+
+// ToUpdatablePacksSummary returns a summary of updatable packages
+func (ps PackageInfoList) ToUpdatablePacksSummary() string {
+	return fmt.Sprintf("%d updatable packages",
+		ps.countUpdatablePacks())
+}
+
 // Find search PackageInfo by name-version-release
 //  func (ps PackageInfoList) find(nameVersionRelease string) (PackageInfo, bool) {
 //      for _, p := range ps {
@@ -287,9 +412,6 @@ func (a PackageInfosByName) Less(i, j int) bool { return a[i].Name < a[j].Name }
 
 // PackageInfo has installed packages.
 type PackageInfo struct {
-	gorm.Model `json:"-" xml:"-"`
-	CveInfoID  uint `json:"-" xml:"-"`
-
 	Name       string
 	Version    string
 	Release    string
@@ -324,9 +446,6 @@ func (p PackageInfo) ToStringNewVersion() string {
 
 // DistroAdvisory has Amazon Linux, RHEL, FreeBSD Security Advisory information.
 type DistroAdvisory struct {
-	gorm.Model `json:"-" xml:"-"`
-	CveInfoID  uint `json:"-" xml:"-"`
-
 	AdvisoryID string
 	Severity   string
 	Issued     time.Time
@@ -335,18 +454,12 @@ type DistroAdvisory struct {
 
 // Container has Container information
 type Container struct {
-	gorm.Model   `json:"-" xml:"-"`
-	ScanResultID uint `json:"-" xml:"-"`
-
 	ContainerID string
 	Name        string
 }
 
 // Platform has platform information
 type Platform struct {
-	gorm.Model   `json:"-" xml:"-"`
-	ScanResultID uint `json:"-" xml:"-"`
-
 	Name       string // aws or azure or gcp or other...
 	InstanceID string
 }
diff --git a/models/models_test.go b/models/models_test.go
index a6fa25e6..0ef1d40e 100644
--- a/models/models_test.go
+++ b/models/models_test.go
@@ -17,9 +17,14 @@ along with this program.  If not, see .
 
 package models
 
-import "testing"
+import (
+	"reflect"
+	"testing"
 
-func TestPackageInfosUniqByName(t *testing.T) {
+	"github.com/k0kubun/pp"
+)
+
+func TestPackageInfoListUniqByName(t *testing.T) {
 	var test = struct {
 		in  PackageInfoList
 		out PackageInfoList
@@ -52,3 +57,81 @@ func TestPackageInfosUniqByName(t *testing.T) {
 		}
 	}
 }
+
+func TestMergeNewVersion(t *testing.T) {
+	var test = struct {
+		a        PackageInfoList
+		b        PackageInfoList
+		expected PackageInfoList
+	}{
+		PackageInfoList{
+			{
+				Name: "hoge",
+			},
+		},
+		PackageInfoList{
+			{
+				Name:       "hoge",
+				NewVersion: "1.0.0",
+				NewRelease: "release1",
+			},
+		},
+		PackageInfoList{
+			{
+				Name:       "hoge",
+				NewVersion: "1.0.0",
+				NewRelease: "release1",
+			},
+		},
+	}
+
+	test.a.MergeNewVersion(test.b)
+	if !reflect.DeepEqual(test.a, test.expected) {
+		e := pp.Sprintf("%v", test.a)
+		a := pp.Sprintf("%v", test.expected)
+		t.Errorf("expected %s, actual %s", e, a)
+	}
+}
+func TestVulnInfosSetGet(t *testing.T) {
+	var test = struct {
+		in  []string
+		out []string
+	}{
+		[]string{
+			"CVE1",
+			"CVE2",
+			"CVE3",
+			"CVE1",
+			"CVE1",
+			"CVE2",
+			"CVE3",
+		},
+		[]string{
+			"CVE1",
+			"CVE2",
+			"CVE3",
+		},
+	}
+
+	//  var ps packageCveInfos
+	var ps VulnInfos
+	for _, cid := range test.in {
+		ps = ps.set(cid, VulnInfo{CveID: cid})
+	}
+
+	if len(test.out) != len(ps) {
+		t.Errorf("length: expected %d, actual %d", len(test.out), len(ps))
+	}
+
+	for i, expectedCid := range test.out {
+		if expectedCid != ps[i].CveID {
+			t.Errorf("expected %s, actual %s", expectedCid, ps[i].CveID)
+		}
+	}
+	for _, cid := range test.in {
+		p, _ := ps.FindByCveID(cid)
+		if p.CveID != cid {
+			t.Errorf("expected %s, actual %s", cid, p.CveID)
+		}
+	}
+}
diff --git a/report/azureblob.go b/report/azureblob.go
index bf2ef35b..2d84a820 100644
--- a/report/azureblob.go
+++ b/report/azureblob.go
@@ -20,6 +20,7 @@ package report
 import (
 	"bytes"
 	"encoding/json"
+	"encoding/xml"
 	"fmt"
 	"time"
 
@@ -27,12 +28,76 @@ import (
 
 	c "github.com/future-architect/vuls/config"
 	"github.com/future-architect/vuls/models"
-	"github.com/future-architect/vuls/util"
 )
 
 // AzureBlobWriter writes results to AzureBlob
 type AzureBlobWriter struct{}
 
+// Write results to Azure Blob storage
+func (w AzureBlobWriter) Write(rs ...models.ScanResult) (err error) {
+	if len(rs) == 0 {
+		return nil
+	}
+
+	cli, err := getBlobClient()
+	if err != nil {
+		return err
+	}
+
+	if c.Conf.FormatOneLineText {
+		timestr := rs[0].ScannedAt.Format(time.RFC3339)
+		k := fmt.Sprintf(timestr + "/summary.txt")
+		text := toOneLineSummary(rs...)
+		b := []byte(text)
+		if err := createBlockBlob(cli, k, b); err != nil {
+			return err
+		}
+	}
+
+	for _, r := range rs {
+		key := r.ReportKeyName()
+		if c.Conf.FormatJSON {
+			k := key + ".json"
+			var b []byte
+			if b, err = json.Marshal(r); err != nil {
+				return fmt.Errorf("Failed to Marshal to JSON: %s", err)
+			}
+			if err := createBlockBlob(cli, k, b); err != nil {
+				return err
+			}
+		}
+
+		if c.Conf.FormatShortText {
+			k := key + "_short.txt"
+			b := []byte(toShortPlainText(r))
+			if err := createBlockBlob(cli, k, b); err != nil {
+				return err
+			}
+		}
+
+		if c.Conf.FormatFullText {
+			k := key + "_full.txt"
+			b := []byte(toFullPlainText(r))
+			if err := createBlockBlob(cli, k, b); err != nil {
+				return err
+			}
+		}
+
+		if c.Conf.FormatXML {
+			k := key + ".xml"
+			var b []byte
+			if b, err = xml.Marshal(r); err != nil {
+				return fmt.Errorf("Failed to Marshal to XML: %s", err)
+			}
+			allBytes := bytes.Join([][]byte{[]byte(xml.Header + vulsOpenTag), b, []byte(vulsCloseTag)}, []byte{})
+			if err := createBlockBlob(cli, k, allBytes); err != nil {
+				return err
+			}
+		}
+	}
+	return
+}
+
 // CheckIfAzureContainerExists check the existence of Azure storage container
 func CheckIfAzureContainerExists() error {
 	cli, err := getBlobClient()
@@ -57,84 +122,24 @@ func getBlobClient() (storage.BlobStorageClient, error) {
 	return api.GetBlobService(), nil
 }
 
-// Write results to Azure Blob storage
-func (w AzureBlobWriter) Write(scanResults []models.ScanResult) (err error) {
-	reqChan := make(chan models.ScanResult, len(scanResults))
-	resChan := make(chan bool)
-	errChan := make(chan error, len(scanResults))
-	defer close(resChan)
-	defer close(errChan)
-	defer close(reqChan)
-
-	timeout := time.After(10 * 60 * time.Second)
-	concurrency := 10
-	tasks := util.GenWorkers(concurrency)
-
-	go func() {
-		for _, r := range scanResults {
-			reqChan <- r
-		}
-	}()
-
-	for range scanResults {
-		tasks <- func() {
-			select {
-			case sresult := <-reqChan:
-				func(r models.ScanResult) {
-					err := w.upload(r)
-					if err != nil {
-						errChan <- err
-					}
-					resChan <- true
-				}(sresult)
-			}
+func createBlockBlob(cli storage.BlobStorageClient, k string, b []byte) error {
+	var err error
+	if c.Conf.GZIP {
+		if b, err = gz(b); err != nil {
+			return err
 		}
+		k = k + ".gz"
 	}
 
-	errs := []error{}
-	for i := 0; i < len(scanResults); i++ {
-		select {
-		case <-resChan:
-		case err := <-errChan:
-			errs = append(errs, err)
-		case <-timeout:
-			errs = append(errs, fmt.Errorf("Timeout while uploading to azure Blob"))
-		}
-	}
-
-	if 0 < len(errs) {
-		return fmt.Errorf("Failed to upload json to Azure Blob: %v", errs)
+	if err := cli.CreateBlockBlobFromReader(
+		c.Conf.AzureContainer,
+		k,
+		uint64(len(b)),
+		bytes.NewReader(b),
+		map[string]string{},
+	); err != nil {
+		return fmt.Errorf("Failed to upload data to %s/%s, %s",
+			c.Conf.AzureContainer, k, err)
 	}
 	return nil
 }
-
-func (w AzureBlobWriter) upload(res models.ScanResult) (err error) {
-	cli, err := getBlobClient()
-	if err != nil {
-		return err
-	}
-	timestr := time.Now().Format(time.RFC3339)
-	name := ""
-	if len(res.Container.ContainerID) == 0 {
-		name = fmt.Sprintf("%s/%s.json", timestr, res.ServerName)
-	} else {
-		name = fmt.Sprintf("%s/%s_%s.json", timestr, res.ServerName, res.Container.Name)
-	}
-
-	jsonBytes, err := json.Marshal(res)
-	if err != nil {
-		return fmt.Errorf("Failed to Marshal to JSON: %s", err)
-	}
-
-	if err = cli.CreateBlockBlobFromReader(
-		c.Conf.AzureContainer,
-		name,
-		uint64(len(jsonBytes)),
-		bytes.NewReader(jsonBytes),
-		map[string]string{},
-	); err != nil {
-		return fmt.Errorf("%s/%s, %s",
-			c.Conf.AzureContainer, name, err)
-	}
-	return
-}
diff --git a/report/mail.go b/report/email.go
similarity index 62%
rename from report/mail.go
rename to report/email.go
index a8ba4271..e2b7c71a 100644
--- a/report/mail.go
+++ b/report/email.go
@@ -29,27 +29,27 @@ import (
 	"github.com/future-architect/vuls/models"
 )
 
-// MailWriter send mail
-type MailWriter struct{}
+// EMailWriter send mail
+type EMailWriter struct{}
 
-func (w MailWriter) Write(scanResults []models.ScanResult) (err error) {
+func (w EMailWriter) Write(rs ...models.ScanResult) (err error) {
 	conf := config.Conf
-	for _, s := range scanResults {
-		to := strings.Join(conf.Mail.To[:], ", ")
-		cc := strings.Join(conf.Mail.Cc[:], ", ")
-		mailAddresses := append(conf.Mail.To, conf.Mail.Cc...)
-		if _, err := mail.ParseAddressList(strings.Join(mailAddresses[:], ", ")); err != nil {
-			return fmt.Errorf("Failed to parse email addresses: %s", err)
-		}
+	to := strings.Join(conf.EMail.To[:], ", ")
+	cc := strings.Join(conf.EMail.Cc[:], ", ")
+	mailAddresses := append(conf.EMail.To, conf.EMail.Cc...)
+	if _, err := mail.ParseAddressList(strings.Join(mailAddresses[:], ", ")); err != nil {
+		return fmt.Errorf("Failed to parse email addresses: %s", err)
+	}
 
+	for _, r := range rs {
 		subject := fmt.Sprintf("%s%s %s",
-			conf.Mail.SubjectPrefix,
-			s.ServerInfo(),
-			s.CveSummary(),
+			conf.EMail.SubjectPrefix,
+			r.ServerInfo(),
+			r.CveSummary(),
 		)
 
 		headers := make(map[string]string)
-		headers["From"] = conf.Mail.From
+		headers["From"] = conf.EMail.From
 		headers["To"] = to
 		headers["Cc"] = cc
 		headers["Subject"] = subject
@@ -60,25 +60,19 @@ func (w MailWriter) Write(scanResults []models.ScanResult) (err error) {
 		for k, v := range headers {
 			message += fmt.Sprintf("%s: %s\r\n", k, v)
 		}
+		message += "\r\n" + toFullPlainText(r)
 
-		var body string
-		if body, err = toPlainText(s); err != nil {
-			return err
-		}
-		message += "\r\n" + body
-
-		smtpServer := net.JoinHostPort(conf.Mail.SMTPAddr, conf.Mail.SMTPPort)
-
-		err := smtp.SendMail(
+		smtpServer := net.JoinHostPort(conf.EMail.SMTPAddr, conf.EMail.SMTPPort)
+		err = smtp.SendMail(
 			smtpServer,
 			smtp.PlainAuth(
 				"",
-				conf.Mail.User,
-				conf.Mail.Password,
-				conf.Mail.SMTPAddr,
+				conf.EMail.User,
+				conf.EMail.Password,
+				conf.EMail.SMTPAddr,
 			),
-			conf.Mail.From,
-			conf.Mail.To,
+			conf.EMail.From,
+			conf.EMail.To,
 			[]byte(message),
 		)
 
diff --git a/report/json.go b/report/json.go
deleted file mode 100644
index 728239ef..00000000
--- a/report/json.go
+++ /dev/null
@@ -1,150 +0,0 @@
-/* Vuls - Vulnerability Scanner
-Copyright (C) 2016  Future Architect, Inc. Japan.
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program.  If not, see .
-*/
-
-package report
-
-import (
-	"encoding/json"
-	"fmt"
-	"io/ioutil"
-	"os"
-	"path/filepath"
-	"regexp"
-	"sort"
-	"strings"
-	"time"
-
-	c "github.com/future-architect/vuls/config"
-	"github.com/future-architect/vuls/models"
-)
-
-// JSONDirs array of json files path.
-type JSONDirs []string
-
-func (d JSONDirs) Len() int {
-	return len(d)
-}
-func (d JSONDirs) Swap(i, j int) {
-	d[i], d[j] = d[j], d[i]
-}
-func (d JSONDirs) Less(i, j int) bool {
-	return d[j] < d[i]
-}
-
-// JSONWriter writes results to file.
-type JSONWriter struct {
-	ScannedAt time.Time
-}
-
-func (w JSONWriter) Write(scanResults []models.ScanResult) (err error) {
-	var path string
-	if path, err = ensureResultDir(w.ScannedAt); err != nil {
-		return fmt.Errorf("Failed to make direcotory/symlink : %s", err)
-	}
-
-	for _, scanResult := range scanResults {
-		scanResult.ScannedAt = w.ScannedAt
-	}
-
-	var jsonBytes []byte
-	for _, r := range scanResults {
-		jsonPath := ""
-		if len(r.Container.ContainerID) == 0 {
-			jsonPath = filepath.Join(path, fmt.Sprintf("%s.json", r.ServerName))
-		} else {
-			jsonPath = filepath.Join(path,
-				fmt.Sprintf("%s_%s.json", r.ServerName, r.Container.Name))
-		}
-
-		if jsonBytes, err = json.Marshal(r); err != nil {
-			return fmt.Errorf("Failed to Marshal to JSON: %s", err)
-		}
-		if err := ioutil.WriteFile(jsonPath, jsonBytes, 0600); err != nil {
-			return fmt.Errorf("Failed to write JSON. path: %s, err: %s", jsonPath, err)
-		}
-	}
-	return nil
-}
-
-// JSONDirPattern is file name pattern of JSON directory
-var JSONDirPattern = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}$`)
-
-// GetValidJSONDirs return valid json directory as array
-func GetValidJSONDirs() (jsonDirs JSONDirs, err error) {
-	var dirInfo []os.FileInfo
-	if dirInfo, err = ioutil.ReadDir(c.Conf.ResultsDir); err != nil {
-		err = fmt.Errorf("Failed to read %s: %s", c.Conf.ResultsDir, err)
-		return
-	}
-	for _, d := range dirInfo {
-		if d.IsDir() && JSONDirPattern.MatchString(d.Name()) {
-			jsonDir := filepath.Join(c.Conf.ResultsDir, d.Name())
-			jsonDirs = append(jsonDirs, jsonDir)
-		}
-	}
-	sort.Sort(jsonDirs)
-	return
-}
-
-// LoadOneScanHistory read JSON data
-func LoadOneScanHistory(jsonDir string) (scanHistory models.ScanHistory, err error) {
-	var scanResults []models.ScanResult
-	var files []os.FileInfo
-	if files, err = ioutil.ReadDir(jsonDir); err != nil {
-		err = fmt.Errorf("Failed to read %s: %s", jsonDir, err)
-		return
-	}
-	for _, file := range files {
-		if filepath.Ext(file.Name()) != ".json" {
-			continue
-		}
-		var scanResult models.ScanResult
-		var data []byte
-		jsonPath := filepath.Join(jsonDir, file.Name())
-		if data, err = ioutil.ReadFile(jsonPath); err != nil {
-			err = fmt.Errorf("Failed to read %s: %s", jsonPath, err)
-			return
-		}
-		if json.Unmarshal(data, &scanResult) != nil {
-			err = fmt.Errorf("Failed to parse %s: %s", jsonPath, err)
-			return
-		}
-		scanResults = append(scanResults, scanResult)
-	}
-	if len(scanResults) == 0 {
-		err = fmt.Errorf("There is no json file under %s", jsonDir)
-		return
-	}
-
-	var scannedAt time.Time
-	if scanResults[0].ScannedAt.IsZero() {
-		splitPath := strings.Split(jsonDir, string(os.PathSeparator))
-		timeStr := splitPath[len(splitPath)-1]
-		if scannedAt, err = time.Parse(time.RFC3339, timeStr); err != nil {
-			err = fmt.Errorf("Failed to parse %s: %s", timeStr, err)
-			return
-		}
-	} else {
-		scannedAt = scanResults[0].ScannedAt
-	}
-
-	scanHistory = models.ScanHistory{
-		ScanResults: scanResults,
-		ScannedAt:   scannedAt,
-	}
-	return
-}
diff --git a/report/localfile.go b/report/localfile.go
new file mode 100644
index 00000000..f8d6e202
--- /dev/null
+++ b/report/localfile.go
@@ -0,0 +1,111 @@
+/* Vuls - Vulnerability Scanner
+Copyright (C) 2016  Future Architect, Inc. Japan.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see .
+*/
+
+package report
+
+import (
+	"bytes"
+	"encoding/json"
+	"encoding/xml"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+
+	c "github.com/future-architect/vuls/config"
+	"github.com/future-architect/vuls/models"
+)
+
+// LocalFileWriter writes results to a local file.
+type LocalFileWriter struct {
+	CurrentDir string
+}
+
+func (w LocalFileWriter) Write(rs ...models.ScanResult) (err error) {
+	if c.Conf.FormatOneLineText {
+		path := filepath.Join(w.CurrentDir, "summary.txt")
+		text := toOneLineSummary(rs...)
+		if err := writeFile(path, []byte(text), 0600); err != nil {
+			return fmt.Errorf(
+				"Failed to write to file. path: %s, err: %s",
+				path, err)
+		}
+	}
+
+	for _, r := range rs {
+		path := filepath.Join(w.CurrentDir, r.ReportFileName())
+
+		if c.Conf.FormatJSON {
+			p := path + ".json"
+			var b []byte
+			if b, err = json.Marshal(r); err != nil {
+				return fmt.Errorf("Failed to Marshal to JSON: %s", err)
+			}
+			if err := writeFile(p, b, 0600); err != nil {
+				return fmt.Errorf("Failed to write JSON. path: %s, err: %s", p, err)
+			}
+		}
+
+		if c.Conf.FormatShortText {
+			p := path + "_short.txt"
+			if err := writeFile(
+				p, []byte(toShortPlainText(r)), 0600); err != nil {
+				return fmt.Errorf(
+					"Failed to write text files. path: %s, err: %s", p, err)
+			}
+		}
+
+		if c.Conf.FormatFullText {
+			p := path + "_full.txt"
+			if err := writeFile(
+				p, []byte(toFullPlainText(r)), 0600); err != nil {
+				return fmt.Errorf(
+					"Failed to write text files. path: %s, err: %s", p, err)
+			}
+		}
+
+		if c.Conf.FormatXML {
+			p := path + ".xml"
+			var b []byte
+			if b, err = xml.Marshal(r); err != nil {
+				return fmt.Errorf("Failed to Marshal to XML: %s", err)
+			}
+			allBytes := bytes.Join([][]byte{[]byte(xml.Header + vulsOpenTag), b, []byte(vulsCloseTag)}, []byte{})
+			if err := writeFile(p, allBytes, 0600); err != nil {
+				return fmt.Errorf("Failed to write XML. path: %s, err: %s", p, err)
+			}
+		}
+	}
+	return nil
+}
+
+func writeFile(path string, data []byte, perm os.FileMode) error {
+	var err error
+	if c.Conf.GZIP {
+		if data, err = gz(data); err != nil {
+			return err
+		}
+		path = path + ".gz"
+	}
+
+	if err := ioutil.WriteFile(
+		path, []byte(data), perm); err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/report/logrus.go b/report/logrus.go
deleted file mode 100644
index c48b2a5f..00000000
--- a/report/logrus.go
+++ /dev/null
@@ -1,56 +0,0 @@
-/* Vuls - Vulnerability Scanner
-Copyright (C) 2016  Future Architect, Inc. Japan.
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program.  If not, see .
-*/
-
-package report
-
-import (
-	"os"
-	"path/filepath"
-	"runtime"
-
-	"github.com/Sirupsen/logrus"
-	"github.com/future-architect/vuls/models"
-	formatter "github.com/kotakanbe/logrus-prefixed-formatter"
-)
-
-// LogrusWriter write to logfile
-type LogrusWriter struct {
-}
-
-func (w LogrusWriter) Write(scanResults []models.ScanResult) error {
-	path := "/var/log/vuls/report.log"
-	if runtime.GOOS == "windows" {
-		path = filepath.Join(os.Getenv("APPDATA"), "vuls", "report.log")
-	}
-	f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600)
-	if err != nil {
-		return err
-	}
-	log := logrus.New()
-	log.Formatter = &formatter.TextFormatter{}
-	log.Out = f
-	log.Level = logrus.InfoLevel
-
-	for _, s := range scanResults {
-		text, err := toPlainText(s)
-		if err != nil {
-			return err
-		}
-		log.Infof(text)
-	}
-	return nil
-}
diff --git a/report/s3.go b/report/s3.go
index 06262303..754d7577 100644
--- a/report/s3.go
+++ b/report/s3.go
@@ -20,6 +20,7 @@ package report
 import (
 	"bytes"
 	"encoding/json"
+	"encoding/xml"
 	"fmt"
 	"time"
 
@@ -32,6 +33,78 @@ import (
 	"github.com/future-architect/vuls/models"
 )
 
+// S3Writer writes results to S3
+type S3Writer struct{}
+
+func getS3() *s3.S3 {
+	return s3.New(session.New(&aws.Config{
+		Region:      aws.String(c.Conf.AwsRegion),
+		Credentials: credentials.NewSharedCredentials("", c.Conf.AwsProfile),
+	}))
+}
+
+// Write results to S3
+// http://docs.aws.amazon.com/sdk-for-go/latest/v1/developerguide/common-examples.title.html
+func (w S3Writer) Write(rs ...models.ScanResult) (err error) {
+	if len(rs) == 0 {
+		return nil
+	}
+
+	svc := getS3()
+
+	if c.Conf.FormatOneLineText {
+		timestr := rs[0].ScannedAt.Format(time.RFC3339)
+		k := fmt.Sprintf(timestr + "/summary.txt")
+		text := toOneLineSummary(rs...)
+		if err := putObject(svc, k, []byte(text)); err != nil {
+			return err
+		}
+	}
+
+	for _, r := range rs {
+		key := r.ReportKeyName()
+		if c.Conf.FormatJSON {
+			k := key + ".json"
+			var b []byte
+			if b, err = json.Marshal(r); err != nil {
+				return fmt.Errorf("Failed to Marshal to JSON: %s", err)
+			}
+			if err := putObject(svc, k, b); err != nil {
+				return err
+			}
+		}
+
+		if c.Conf.FormatShortText {
+			k := key + "_short.txt"
+			text := toShortPlainText(r)
+			if err := putObject(svc, k, []byte(text)); err != nil {
+				return err
+			}
+		}
+
+		if c.Conf.FormatFullText {
+			k := key + "_full.txt"
+			text := toFullPlainText(r)
+			if err := putObject(svc, k, []byte(text)); err != nil {
+				return err
+			}
+		}
+
+		if c.Conf.FormatXML {
+			k := key + ".xml"
+			var b []byte
+			if b, err = xml.Marshal(r); err != nil {
+				return fmt.Errorf("Failed to Marshal to XML: %s", err)
+			}
+			allBytes := bytes.Join([][]byte{[]byte(xml.Header + vulsOpenTag), b, []byte(vulsCloseTag)}, []byte{})
+			if err := putObject(svc, k, allBytes); err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
 // CheckIfBucketExists check the existence of S3 bucket
 func CheckIfBucketExists() error {
 	svc := getS3()
@@ -57,46 +130,22 @@ func CheckIfBucketExists() error {
 	return nil
 }
 
-// S3Writer writes results to S3
-type S3Writer struct{}
-
-func getS3() *s3.S3 {
-	return s3.New(session.New(&aws.Config{
-		Region:      aws.String(c.Conf.AwsRegion),
-		Credentials: credentials.NewSharedCredentials("", c.Conf.AwsProfile),
-	}))
-}
-
-// Write results to S3
-func (w S3Writer) Write(scanResults []models.ScanResult) (err error) {
-
-	var jsonBytes []byte
-	if jsonBytes, err = json.Marshal(scanResults); err != nil {
-		return fmt.Errorf("Failed to Marshal to JSON: %s", err)
+func putObject(svc *s3.S3, k string, b []byte) error {
+	var err error
+	if c.Conf.GZIP {
+		if b, err = gz(b); err != nil {
+			return err
+		}
+		k = k + ".gz"
 	}
 
-	// http://docs.aws.amazon.com/sdk-for-go/latest/v1/developerguide/common-examples.title.html
-	svc := getS3()
-	timestr := time.Now().Format(time.RFC3339)
-	for _, r := range scanResults {
-		key := ""
-		if len(r.Container.ContainerID) == 0 {
-			key = fmt.Sprintf("%s/%s.json", timestr, r.ServerName)
-		} else {
-			key = fmt.Sprintf("%s/%s_%s.json", timestr, r.ServerName, r.Container.Name)
-		}
-
-		if jsonBytes, err = json.Marshal(r); err != nil {
-			return fmt.Errorf("Failed to Marshal to JSON: %s", err)
-		}
-		_, err = svc.PutObject(&s3.PutObjectInput{
-			Bucket: &c.Conf.S3Bucket,
-			Key:    &key,
-			Body:   bytes.NewReader(jsonBytes),
-		})
-		if err != nil {
-			return fmt.Errorf("Failed to upload data to %s/%s, %s", c.Conf.S3Bucket, key, err)
-		}
+	if _, err := svc.PutObject(&s3.PutObjectInput{
+		Bucket: &c.Conf.S3Bucket,
+		Key:    &k,
+		Body:   bytes.NewReader(b),
+	}); err != nil {
+		return fmt.Errorf("Failed to upload data to %s/%s, %s",
+			c.Conf.S3Bucket, k, err)
 	}
 	return nil
 }
diff --git a/report/slack.go b/report/slack.go
index 655445e6..d38e49ca 100644
--- a/report/slack.go
+++ b/report/slack.go
@@ -56,36 +56,36 @@ type message struct {
 // SlackWriter send report to slack
 type SlackWriter struct{}
 
-func (w SlackWriter) Write(scanResults []models.ScanResult) error {
+func (w SlackWriter) Write(rs ...models.ScanResult) error {
 	conf := config.Conf.Slack
-	for _, s := range scanResults {
-		channel := conf.Channel
+	channel := conf.Channel
+
+	for _, r := range rs {
 		if channel == "${servername}" {
-			channel = fmt.Sprintf("#%s", s.ServerName)
+			channel = fmt.Sprintf("#%s", r.ServerName)
 		}
 
 		msg := message{
-			Text:        msgText(s),
+			Text:        msgText(r),
 			Username:    conf.AuthUser,
 			IconEmoji:   conf.IconEmoji,
 			Channel:     channel,
-			Attachments: toSlackAttachments(s),
+			Attachments: toSlackAttachments(r),
 		}
 
 		bytes, _ := json.Marshal(msg)
 		jsonBody := string(bytes)
 		f := func() (err error) {
-			resp, body, errs := gorequest.New().Proxy(config.Conf.HTTPProxy).Post(conf.HookURL).
-				Send(string(jsonBody)).End()
-			if resp.StatusCode != 200 {
-				log.Errorf("Resonse body: %s", body)
-				if 0 < len(errs) {
-					return errs[0]
-				}
+			resp, body, errs := gorequest.New().Proxy(config.Conf.HTTPProxy).Post(conf.HookURL).Send(string(jsonBody)).End()
+			if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
+				return fmt.Errorf(
+					"HTTP POST error: %v, url: %s, resp: %v, body: %s",
+					errs, conf.HookURL, resp, body)
 			}
 			return nil
 		}
 		notify := func(err error, t time.Duration) {
+			log.Warn("Error %s", err)
 			log.Warn("Retrying in ", t)
 		}
 		if err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify); err != nil {
@@ -118,8 +118,8 @@ func toSlackAttachments(scanResult models.ScanResult) (attaches []*attachment) {
 		for _, p := range cveInfo.Packages {
 			curentPackages = append(curentPackages, p.ToStringCurrentVersion())
 		}
-		for _, cpename := range cveInfo.CpeNames {
-			curentPackages = append(curentPackages, cpename.Name)
+		for _, n := range cveInfo.CpeNames {
+			curentPackages = append(curentPackages, n)
 		}
 
 		newPackages := []string{}
diff --git a/report/stdout.go b/report/stdout.go
index eb7dbaa5..ef963c58 100644
--- a/report/stdout.go
+++ b/report/stdout.go
@@ -20,19 +20,40 @@ package report
 import (
 	"fmt"
 
+	c "github.com/future-architect/vuls/config"
 	"github.com/future-architect/vuls/models"
 )
 
 // StdoutWriter write to stdout
 type StdoutWriter struct{}
 
-func (w StdoutWriter) Write(scanResults []models.ScanResult) error {
-	for _, s := range scanResults {
-		text, err := toPlainText(s)
-		if err != nil {
-			return err
+// WriteScanSummary prints Scan summary at the end of scan
+func (w StdoutWriter) WriteScanSummary(rs ...models.ScanResult) {
+	fmt.Printf("\n\n")
+	fmt.Printf("Scan Summary\n")
+	fmt.Printf("============\n")
+	fmt.Printf("%s\n", toScanSummary(rs...))
+}
+
+func (w StdoutWriter) Write(rs ...models.ScanResult) error {
+	if c.Conf.FormatOneLineText {
+		fmt.Print("\n\n")
+		fmt.Println("One Line Summary")
+		fmt.Println("================")
+		fmt.Println(toOneLineSummary(rs...))
+		fmt.Print("\n")
+	}
+
+	if c.Conf.FormatShortText {
+		for _, r := range rs {
+			fmt.Println(toShortPlainText(r))
+		}
+	}
+
+	if c.Conf.FormatFullText {
+		for _, r := range rs {
+			fmt.Println(toFullPlainText(r))
 		}
-		fmt.Println(text)
 	}
 	return nil
 }
diff --git a/report/textfile.go b/report/textfile.go
deleted file mode 100644
index d93a7e66..00000000
--- a/report/textfile.go
+++ /dev/null
@@ -1,64 +0,0 @@
-/* Vuls - Vulnerability Scanner
-Copyright (C) 2016  Future Architect, Inc. Japan.
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program.  If not, see .
-*/
-
-package report
-
-import (
-	"fmt"
-	"io/ioutil"
-	"path/filepath"
-	"strings"
-	"time"
-
-	"github.com/future-architect/vuls/models"
-)
-
-// TextFileWriter writes results to file.
-type TextFileWriter struct {
-	ScannedAt time.Time
-}
-
-func (w TextFileWriter) Write(scanResults []models.ScanResult) (err error) {
-	path, err := ensureResultDir(w.ScannedAt)
-	all := []string{}
-	for _, r := range scanResults {
-		textFilePath := ""
-		if len(r.Container.ContainerID) == 0 {
-			textFilePath = filepath.Join(path, fmt.Sprintf("%s.txt", r.ServerName))
-		} else {
-			textFilePath = filepath.Join(path,
-				fmt.Sprintf("%s_%s.txt", r.ServerName, r.Container.Name))
-		}
-		text, err := toPlainText(r)
-		if err != nil {
-			return err
-		}
-		all = append(all, text)
-		b := []byte(text)
-		if err := ioutil.WriteFile(textFilePath, b, 0600); err != nil {
-			return fmt.Errorf("Failed to write text files. path: %s, err: %s", textFilePath, err)
-		}
-	}
-
-	text := strings.Join(all, "\n\n")
-	b := []byte(text)
-	allPath := filepath.Join(path, "all.txt")
-	if err := ioutil.WriteFile(allPath, b, 0600); err != nil {
-		return fmt.Errorf("Failed to write text files. path: %s, err: %s", allPath, err)
-	}
-	return nil
-}
diff --git a/report/tui.go b/report/tui.go
index bf7ad6d5..ce5c9965 100644
--- a/report/tui.go
+++ b/report/tui.go
@@ -20,7 +20,7 @@ package report
 import (
 	"bytes"
 	"fmt"
-	"path/filepath"
+	"os"
 	"strings"
 	"text/template"
 	"time"
@@ -40,13 +40,8 @@ var currentCveInfo int
 var currentDetailLimitY int
 
 // RunTui execute main logic
-func RunTui(jsonDirName string) subcommands.ExitStatus {
-	var err error
-	scanHistory, err = selectScanHistory(jsonDirName)
-	if err != nil {
-		log.Errorf("%s", err)
-		return subcommands.ExitFailure
-	}
+func RunTui(history models.ScanHistory) subcommands.ExitStatus {
+	scanHistory = history
 
 	g, err := gocui.NewGui(gocui.OutputNormal)
 	if err != nil {
@@ -64,34 +59,14 @@ func RunTui(jsonDirName string) subcommands.ExitStatus {
 	g.SelFgColor = gocui.ColorBlack
 	g.Cursor = true
 
-	if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
+	if err := g.MainLoop(); err != nil {
+		g.Close()
 		log.Errorf("%s", err)
-		return subcommands.ExitFailure
+		os.Exit(1)
 	}
-
 	return subcommands.ExitSuccess
 }
 
-func selectScanHistory(jsonDirName string) (latest models.ScanHistory, err error) {
-	var jsonDir string
-	if 0 < len(jsonDirName) {
-		jsonDir = filepath.Join(config.Conf.ResultsDir, jsonDirName)
-	} else {
-		var jsonDirs JSONDirs
-		if jsonDirs, err = GetValidJSONDirs(); err != nil {
-			return
-		}
-		if len(jsonDirs) == 0 {
-			return latest, fmt.Errorf("No scan results are found in %s", config.Conf.ResultsDir)
-		}
-		jsonDir = jsonDirs[0]
-	}
-	if latest, err = LoadOneScanHistory(jsonDir); err != nil {
-		return
-	}
-	return
-}
-
 func keybindings(g *gocui.Gui) (err error) {
 	errs := []error{}
 
@@ -537,6 +512,9 @@ func setSideLayout(g *gocui.Gui) error {
 		for _, result := range scanHistory.ScanResults {
 			fmt.Fprintln(v, result.ServerInfoTui())
 		}
+		if len(scanHistory.ScanResults) == 0 {
+			return fmt.Errorf("No scan results")
+		}
 		currentScanResult = scanHistory.ScanResults[0]
 		if _, err := g.SetCurrentView("side"); err != nil {
 			return err
@@ -666,7 +644,7 @@ type dataForTmpl struct {
 	VulnSiteLinks    []string
 	References       []cve.Reference
 	Packages         []string
-	CpeNames         []models.CpeName
+	CpeNames         []string
 	PublishedDate    time.Time
 	LastModifiedDate time.Time
 }
@@ -780,8 +758,8 @@ Package/CPE
 {{range $pack := .Packages -}}
 * {{$pack}}
 {{end -}}
-{{range .CpeNames -}}
-* {{.Name}}
+{{range $name := .CpeNames -}}
+* {{$name}}
 {{end}}
 Links
 --------------
diff --git a/report/util.go b/report/util.go
index e236c750..667abe21 100644
--- a/report/util.go
+++ b/report/util.go
@@ -20,89 +20,49 @@ package report
 import (
 	"bytes"
 	"fmt"
-	"os"
-	"path/filepath"
 	"strings"
-	"time"
 
 	"github.com/future-architect/vuls/config"
 	"github.com/future-architect/vuls/models"
 	"github.com/gosuri/uitable"
 )
 
-func ensureResultDir(scannedAt time.Time) (path string, err error) {
-	jsonDirName := scannedAt.Format(time.RFC3339)
+const maxColWidth = 80
 
-	resultsDir := config.Conf.ResultsDir
-	if len(resultsDir) == 0 {
-		wd, _ := os.Getwd()
-		resultsDir = filepath.Join(wd, "results")
-	}
-	jsonDir := filepath.Join(resultsDir, jsonDirName)
-
-	if err := os.MkdirAll(jsonDir, 0700); err != nil {
-		return "", fmt.Errorf("Failed to create dir: %s", err)
-	}
-
-	symlinkPath := filepath.Join(resultsDir, "current")
-	if _, err := os.Lstat(symlinkPath); err == nil {
-		if err := os.Remove(symlinkPath); err != nil {
-			return "", fmt.Errorf(
-				"Failed to remove symlink. path: %s, err: %s", symlinkPath, err)
+func toScanSummary(rs ...models.ScanResult) string {
+	table := uitable.New()
+	table.MaxColWidth = maxColWidth
+	table.Wrap = true
+	for _, r := range rs {
+		cols := []interface{}{
+			r.ServerName,
+			fmt.Sprintf("%s%s", r.Family, r.Release),
+			fmt.Sprintf("%d CVEs", len(r.ScannedCves)),
+			r.Packages.ToUpdatablePacksSummary(),
 		}
+		table.AddRow(cols...)
 	}
-
-	if err := os.Symlink(jsonDir, symlinkPath); err != nil {
-		return "", fmt.Errorf(
-			"Failed to create symlink: path: %s, err: %s", symlinkPath, err)
-	}
-	return jsonDir, nil
+	return fmt.Sprintf("%s\n", table)
 }
 
-func toPlainText(scanResult models.ScanResult) (string, error) {
-	serverInfo := scanResult.ServerInfo()
-
-	var buffer bytes.Buffer
-	for i := 0; i < len(serverInfo); i++ {
-		buffer.WriteString("=")
+func toOneLineSummary(rs ...models.ScanResult) string {
+	table := uitable.New()
+	table.MaxColWidth = maxColWidth
+	table.Wrap = true
+	for _, r := range rs {
+		cols := []interface{}{
+			r.ServerName,
+			r.CveSummary(),
+			r.Packages.ToUpdatablePacksSummary(),
+		}
+		table.AddRow(cols...)
 	}
-	header := fmt.Sprintf("%s\n%s", serverInfo, buffer.String())
-
-	if len(scanResult.KnownCves) == 0 && len(scanResult.UnknownCves) == 0 {
-		return fmt.Sprintf(`
-%s
-No unsecure packages.
-`, header), nil
-	}
-
-	summary := ToPlainTextSummary(scanResult)
-	scoredReport, unscoredReport := []string{}, []string{}
-	scoredReport, unscoredReport = toPlainTextDetails(scanResult, scanResult.Family)
-
-	scored := strings.Join(scoredReport, "\n\n")
-
-	unscored := ""
-	if !config.Conf.IgnoreUnscoredCves {
-		unscored = strings.Join(unscoredReport, "\n\n")
-	}
-
-	detail := fmt.Sprintf(`
-%s
-
-%s
-`,
-		scored,
-		unscored,
-	)
-	text := fmt.Sprintf("%s\n%s\n%s\n", header, summary, detail)
-
-	return text, nil
+	return fmt.Sprintf("%s\n", table)
 }
 
-// ToPlainTextSummary format summary for plain text.
-func ToPlainTextSummary(r models.ScanResult) string {
+func toShortPlainText(r models.ScanResult) string {
 	stable := uitable.New()
-	stable.MaxColWidth = 84
+	stable.MaxColWidth = maxColWidth
 	stable.Wrap = true
 
 	cves := r.KnownCves
@@ -110,14 +70,45 @@ func ToPlainTextSummary(r models.ScanResult) string {
 		cves = append(cves, r.UnknownCves...)
 	}
 
-	for _, d := range cves {
-		var scols []string
+	var buf bytes.Buffer
+	for i := 0; i < len(r.ServerInfo()); i++ {
+		buf.WriteString("=")
+	}
+	header := fmt.Sprintf("%s\n%s\n%s\t%s\n\n",
+		r.ServerInfo(),
+		buf.String(),
+		r.CveSummary(),
+		r.Packages.ToUpdatablePacksSummary(),
+	)
 
+	if len(cves) == 0 {
+		return fmt.Sprintf(`
+%s
+No CVE-IDs are found in updatable packages.
+%s
+`, header, r.Packages.ToUpdatablePacksSummary())
+	}
+
+	for _, d := range cves {
+		var packsVer string
+		for _, p := range d.Packages {
+			packsVer += fmt.Sprintf(
+				"%s -> %s\n", p.ToStringCurrentVersion(), p.ToStringNewVersion())
+		}
+		for _, n := range d.CpeNames {
+			packsVer += n
+		}
+
+		var scols []string
 		switch {
 		case config.Conf.Lang == "ja" &&
 			0 < d.CveDetail.Jvn.CvssScore():
-
-			summary := d.CveDetail.Jvn.CveTitle()
+			summary := fmt.Sprintf("%s\n%s\n%s\n%s",
+				d.CveDetail.Jvn.CveTitle(),
+				d.CveDetail.Jvn.Link(),
+				distroLinks(d, r.Family)[0].url,
+				packsVer,
+			)
 			scols = []string{
 				d.CveDetail.CveID,
 				fmt.Sprintf("%-4.1f (%s)",
@@ -126,8 +117,15 @@ func ToPlainTextSummary(r models.ScanResult) string {
 				),
 				summary,
 			}
+
 		case 0 < d.CveDetail.CvssScore("en"):
-			summary := d.CveDetail.Nvd.CveSummary()
+			summary := fmt.Sprintf("%s\n%s/%s\n%s\n%s",
+				d.CveDetail.Nvd.CveSummary(),
+				cveDetailsBaseURL,
+				d.CveDetail.CveID,
+				distroLinks(d, r.Family)[0].url,
+				packsVer,
+			)
 			scols = []string{
 				d.CveDetail.CveID,
 				fmt.Sprintf("%-4.1f (%s)",
@@ -137,10 +135,12 @@ func ToPlainTextSummary(r models.ScanResult) string {
 				summary,
 			}
 		default:
+			summary := fmt.Sprintf("%s\n%s",
+				distroLinks(d, r.Family)[0].url, packsVer)
 			scols = []string{
 				d.CveDetail.CveID,
 				"?",
-				d.CveDetail.Nvd.CveSummary(),
+				summary,
 			}
 		}
 
@@ -149,12 +149,55 @@ func ToPlainTextSummary(r models.ScanResult) string {
 			cols[i] = scols[i]
 		}
 		stable.AddRow(cols...)
+		stable.AddRow("")
 	}
-	return fmt.Sprintf("%s", stable)
+	return fmt.Sprintf("%s\n%s\n", header, stable)
 }
 
-func toPlainTextDetails(data models.ScanResult, osFamily string) (scoredReport, unscoredReport []string) {
-	for _, cve := range data.KnownCves {
+func toFullPlainText(r models.ScanResult) string {
+	serverInfo := r.ServerInfo()
+
+	var buf bytes.Buffer
+	for i := 0; i < len(serverInfo); i++ {
+		buf.WriteString("=")
+	}
+	header := fmt.Sprintf("%s\n%s\n%s\t%s\n",
+		r.ServerInfo(),
+		buf.String(),
+		r.CveSummary(),
+		r.Packages.ToUpdatablePacksSummary(),
+	)
+
+	if len(r.KnownCves) == 0 && len(r.UnknownCves) == 0 {
+		return fmt.Sprintf(`
+%s
+No CVE-IDs are found in updatable packages.
+%s
+`, header, r.Packages.ToUpdatablePacksSummary())
+	}
+
+	scoredReport, unscoredReport := []string{}, []string{}
+	scoredReport, unscoredReport = toPlainTextDetails(r, r.Family)
+
+	unscored := ""
+	if !config.Conf.IgnoreUnscoredCves {
+		unscored = strings.Join(unscoredReport, "\n\n")
+	}
+
+	scored := strings.Join(scoredReport, "\n\n")
+	detail := fmt.Sprintf(`
+%s
+
+%s
+`,
+		scored,
+		unscored,
+	)
+	return fmt.Sprintf("%s\n%s\n", header, detail)
+}
+
+func toPlainTextDetails(r models.ScanResult, osFamily string) (scoredReport, unscoredReport []string) {
+	for _, cve := range r.KnownCves {
 		switch config.Conf.Lang {
 		case "en":
 			if 0 < cve.CveDetail.Nvd.CvssScore() {
@@ -177,7 +220,7 @@ func toPlainTextDetails(data models.ScanResult, osFamily string) (scoredReport,
 			}
 		}
 	}
-	for _, cve := range data.UnknownCves {
+	for _, cve := range r.UnknownCves {
 		unscoredReport = append(
 			unscoredReport, toPlainTextUnknownCve(cve, osFamily))
 	}
@@ -187,7 +230,7 @@ func toPlainTextDetails(data models.ScanResult, osFamily string) (scoredReport,
 func toPlainTextUnknownCve(cveInfo models.CveInfo, osFamily string) string {
 	cveID := cveInfo.CveDetail.CveID
 	dtable := uitable.New()
-	dtable.MaxColWidth = 100
+	dtable.MaxColWidth = maxColWidth
 	dtable.Wrap = true
 	dtable.AddRow(cveID)
 	dtable.AddRow("-------------")
@@ -201,6 +244,8 @@ func toPlainTextUnknownCve(cveInfo models.CveInfo, osFamily string) string {
 	for _, link := range dlinks {
 		dtable.AddRow(link.title, link.url)
 	}
+	dtable = addPackageInfos(dtable, cveInfo.Packages)
+	dtable = addCpeNames(dtable, cveInfo.CpeNames)
 
 	return fmt.Sprintf("%s", dtable)
 }
@@ -211,7 +256,7 @@ func toPlainTextDetailsLangJa(cveInfo models.CveInfo, osFamily string) string {
 	jvn := cveDetail.Jvn
 
 	dtable := uitable.New()
-	dtable.MaxColWidth = 100
+	dtable.MaxColWidth = maxColWidth
 	dtable.Wrap = true
 	dtable.AddRow(cveID)
 	dtable.AddRow("-------------")
@@ -253,7 +298,7 @@ func toPlainTextDetailsLangEn(d models.CveInfo, osFamily string) string {
 	nvd := cveDetail.Nvd
 
 	dtable := uitable.New()
-	dtable.MaxColWidth = 100
+	dtable.MaxColWidth = maxColWidth
 	dtable.Wrap = true
 	dtable.AddRow(cveID)
 	dtable.AddRow("-------------")
@@ -357,13 +402,12 @@ func distroLinks(cveInfo models.CveInfo, osFamily string) []distroLink {
 	}
 }
 
-//TODO
 // addPackageInfos add package information related the CVE to table
 func addPackageInfos(table *uitable.Table, packs []models.PackageInfo) *uitable.Table {
 	for i, p := range packs {
 		var title string
 		if i == 0 {
-			title = "Package/CPE"
+			title = "Package"
 		}
 		ver := fmt.Sprintf(
 			"%s -> %s", p.ToStringCurrentVersion(), p.ToStringNewVersion())
@@ -372,9 +416,9 @@ func addPackageInfos(table *uitable.Table, packs []models.PackageInfo) *uitable.
 	return table
 }
 
-func addCpeNames(table *uitable.Table, names []models.CpeName) *uitable.Table {
-	for _, p := range names {
-		table.AddRow("CPE", fmt.Sprintf("%s", p.Name))
+func addCpeNames(table *uitable.Table, names []string) *uitable.Table {
+	for _, n := range names {
+		table.AddRow("CPE", fmt.Sprintf("%s", n))
 	}
 	return table
 }
diff --git a/report/writer.go b/report/writer.go
index 13fd22f2..9f7e1dce 100644
--- a/report/writer.go
+++ b/report/writer.go
@@ -17,7 +17,12 @@ along with this program.  If not, see .
 
 package report
 
-import "github.com/future-architect/vuls/models"
+import (
+	"bytes"
+	"compress/gzip"
+
+	"github.com/future-architect/vuls/models"
+)
 
 const (
 	nvdBaseURL            = "https://web.nvd.nist.gov/view/vuln/detail"
@@ -33,9 +38,27 @@ const (
 	debianTrackerBaseURL  = "https://security-tracker.debian.org/tracker"
 
 	freeBSDVuXMLBaseURL = "https://vuxml.freebsd.org/freebsd/%s.html"
+
+	vulsOpenTag  = ""
+	vulsCloseTag = ""
 )
 
 // ResultWriter Interface
 type ResultWriter interface {
-	Write([]models.ScanResult) error
+	Write(...models.ScanResult) error
+}
+
+func gz(data []byte) ([]byte, error) {
+	var b bytes.Buffer
+	gz := gzip.NewWriter(&b)
+	if _, err := gz.Write(data); err != nil {
+		return nil, err
+	}
+	if err := gz.Flush(); err != nil {
+		return nil, err
+	}
+	if err := gz.Close(); err != nil {
+		return nil, err
+	}
+	return b.Bytes(), nil
 }
diff --git a/report/xml.go b/report/xml.go
deleted file mode 100644
index 5699e171..00000000
--- a/report/xml.go
+++ /dev/null
@@ -1,54 +0,0 @@
-package report
-
-import (
-	"bytes"
-	"encoding/xml"
-	"fmt"
-	"io/ioutil"
-	"path/filepath"
-	"time"
-
-	"github.com/future-architect/vuls/models"
-)
-
-const (
-	vulsOpenTag  = ""
-	vulsCloseTag = ""
-)
-
-// XMLWriter writes results to file.
-type XMLWriter struct {
-	ScannedAt time.Time
-}
-
-func (w XMLWriter) Write(scanResults []models.ScanResult) (err error) {
-	var path string
-	if path, err = ensureResultDir(w.ScannedAt); err != nil {
-		return fmt.Errorf("Failed to make direcotory/symlink : %s", err)
-	}
-
-	for _, scanResult := range scanResults {
-		scanResult.ScannedAt = w.ScannedAt
-	}
-
-	var xmlBytes []byte
-	for _, r := range scanResults {
-		xmlPath := ""
-		if len(r.Container.ContainerID) == 0 {
-			xmlPath = filepath.Join(path, fmt.Sprintf("%s.xml", r.ServerName))
-		} else {
-			xmlPath = filepath.Join(path,
-				fmt.Sprintf("%s_%s.xml", r.ServerName, r.Container.Name))
-		}
-
-		if xmlBytes, err = xml.Marshal(r); err != nil {
-			return fmt.Errorf("Failed to Marshal to XML: %s", err)
-		}
-
-		allBytes := bytes.Join([][]byte{[]byte(xml.Header + vulsOpenTag), xmlBytes, []byte(vulsCloseTag)}, []byte{})
-		if err := ioutil.WriteFile(xmlPath, allBytes, 0600); err != nil {
-			return fmt.Errorf("Failed to write XML. path: %s, err: %s", xmlPath, err)
-		}
-	}
-	return nil
-}
diff --git a/scan/base.go b/scan/base.go
index 04485ea4..70c05709 100644
--- a/scan/base.go
+++ b/scan/base.go
@@ -26,7 +26,6 @@ import (
 
 	"github.com/Sirupsen/logrus"
 	"github.com/future-architect/vuls/config"
-	"github.com/future-architect/vuls/cveapi"
 	"github.com/future-architect/vuls/models"
 )
 
@@ -224,62 +223,16 @@ func (l base) isAwsInstanceID(str string) bool {
 }
 
 func (l *base) convertToModel() (models.ScanResult, error) {
-	var scoredCves, unscoredCves, ignoredCves models.CveInfos
-	for _, p := range l.UnsecurePackages {
-		sort.Sort(models.PackageInfosByName(p.Packs))
-
-		// ignoreCves
-		found := false
-		for _, icve := range l.getServerInfo().IgnoreCves {
-			if icve == p.CveDetail.CveID {
-				ignoredCves = append(ignoredCves, models.CveInfo{
-					CveDetail:        p.CveDetail,
-					Packages:         p.Packs,
-					DistroAdvisories: p.DistroAdvisories,
-				})
-				found = true
-				break
-			}
-		}
-		if found {
-			continue
-		}
-
-		// unscoredCves
-		if p.CveDetail.CvssScore(config.Conf.Lang) <= 0 {
-			unscoredCves = append(unscoredCves, models.CveInfo{
-				CveDetail:        p.CveDetail,
-				Packages:         p.Packs,
-				DistroAdvisories: p.DistroAdvisories,
-			})
-			continue
-		}
-
-		cpenames := []models.CpeName{}
-		for _, cpename := range p.CpeNames {
-			cpenames = append(cpenames,
-				models.CpeName{Name: cpename})
-		}
-
-		// scoredCves
-		cve := models.CveInfo{
-			CveDetail:        p.CveDetail,
-			Packages:         p.Packs,
-			DistroAdvisories: p.DistroAdvisories,
-			CpeNames:         cpenames,
-		}
-		scoredCves = append(scoredCves, cve)
+	for _, p := range l.VulnInfos {
+		sort.Sort(models.PackageInfosByName(p.Packages))
 	}
+	sort.Sort(l.VulnInfos)
 
 	container := models.Container{
 		ContainerID: l.ServerInfo.Container.ContainerID,
 		Name:        l.ServerInfo.Container.Name,
 	}
 
-	sort.Sort(scoredCves)
-	sort.Sort(unscoredCves)
-	sort.Sort(ignoredCves)
-
 	return models.ScanResult{
 		ServerName:  l.ServerInfo.ServerName,
 		ScannedAt:   time.Now(),
@@ -287,52 +240,12 @@ func (l *base) convertToModel() (models.ScanResult, error) {
 		Release:     l.Distro.Release,
 		Container:   container,
 		Platform:    l.Platform,
-		KnownCves:   scoredCves,
-		UnknownCves: unscoredCves,
-		IgnoredCves: ignoredCves,
+		ScannedCves: l.VulnInfos,
+		Packages:    l.Packages,
 		Optional:    l.ServerInfo.Optional,
 	}, nil
 }
 
-// scanVulnByCpeName search vulnerabilities that specified in config file.
-func (l *base) scanVulnByCpeName() error {
-	unsecurePacks := CvePacksList{}
-
-	serverInfo := l.getServerInfo()
-	cpeNames := serverInfo.CpeNames
-
-	// remove duplicate
-	set := map[string]CvePacksInfo{}
-
-	for _, name := range cpeNames {
-		details, err := cveapi.CveClient.FetchCveDetailsByCpeName(name)
-		if err != nil {
-			return err
-		}
-		for _, detail := range details {
-			if val, ok := set[detail.CveID]; ok {
-				names := val.CpeNames
-				names = append(names, name)
-				val.CpeNames = names
-				set[detail.CveID] = val
-			} else {
-				set[detail.CveID] = CvePacksInfo{
-					CveID:     detail.CveID,
-					CveDetail: detail,
-					CpeNames:  []string{name},
-				}
-			}
-		}
-	}
-
-	for key := range set {
-		unsecurePacks = append(unsecurePacks, set[key])
-	}
-	unsecurePacks = append(unsecurePacks, l.UnsecurePackages...)
-	l.setUnsecurePackages(unsecurePacks)
-	return nil
-}
-
 func (l *base) setErrs(errs []error) {
 	l.errs = errs
 }
diff --git a/scan/debian.go b/scan/debian.go
index 32b7b5ad..22f4248d 100644
--- a/scan/debian.go
+++ b/scan/debian.go
@@ -26,7 +26,6 @@ import (
 
 	"github.com/future-architect/vuls/cache"
 	"github.com/future-architect/vuls/config"
-	"github.com/future-architect/vuls/cveapi"
 	"github.com/future-architect/vuls/models"
 	"github.com/future-architect/vuls/util"
 )
@@ -179,12 +178,12 @@ func (o *debian) scanPackages() error {
 	}
 	o.setPackages(packs)
 
-	var unsecurePacks []CvePacksInfo
+	var unsecurePacks []models.VulnInfo
 	if unsecurePacks, err = o.scanUnsecurePackages(packs); err != nil {
 		o.log.Errorf("Failed to scan vulnerable packages")
 		return err
 	}
-	o.setUnsecurePackages(unsecurePacks)
+	o.setVulnInfos(unsecurePacks)
 	return nil
 }
 
@@ -242,37 +241,41 @@ func (o *debian) checkRequiredPackagesInstalled() error {
 	return nil
 }
 
-func (o *debian) scanUnsecurePackages(packs []models.PackageInfo) ([]CvePacksInfo, error) {
+func (o *debian) scanUnsecurePackages(installed []models.PackageInfo) ([]models.VulnInfo, error) {
 	o.log.Infof("apt-get update...")
 	cmd := util.PrependProxyEnv("apt-get update")
 	if r := o.ssh(cmd, sudo); !r.isSuccess() {
 		return nil, fmt.Errorf("Failed to SSH: %s", r)
 	}
 
-	upgradablePackNames, err := o.GetUpgradablePackNames()
+	// Convert the name of upgradable packages to PackageInfo struct
+	upgradableNames, err := o.GetUpgradablePackNames()
 	if err != nil {
-		return []CvePacksInfo{}, err
+		return nil, err
 	}
-
-	// Convert package name to PackageInfo struct
-	var unsecurePacks []models.PackageInfo
-	for _, name := range upgradablePackNames {
-		for _, pack := range packs {
+	var upgradablePacks []models.PackageInfo
+	for _, name := range upgradableNames {
+		for _, pack := range installed {
 			if pack.Name == name {
-				unsecurePacks = append(unsecurePacks, pack)
+				upgradablePacks = append(upgradablePacks, pack)
 				break
 			}
 		}
 	}
-	unsecurePacks, err = o.fillCandidateVersion(unsecurePacks)
+
+	// Fill the candidate versions of upgradable packages
+	upgradablePacks, err = o.fillCandidateVersion(upgradablePacks)
 	if err != nil {
 		return nil, fmt.Errorf("Failed to fill candidate versions. err: %s", err)
 	}
 
+	o.Packages.MergeNewVersion(upgradablePacks)
+
+	// Setup changelog cache
 	current := cache.Meta{
 		Name:   o.getServerInfo().GetServerName(),
 		Distro: o.getServerInfo().Distro,
-		Packs:  unsecurePacks,
+		Packs:  upgradablePacks,
 	}
 	o.log.Debugf("Ensure changelog cache: %s", current.Name)
 	if err := o.ensureChangelogCache(current); err != nil {
@@ -280,12 +283,12 @@ func (o *debian) scanUnsecurePackages(packs []models.PackageInfo) ([]CvePacksInf
 	}
 
 	// Collect CVE information of upgradable packages
-	cvePacksInfos, err := o.scanPackageCveInfos(unsecurePacks)
+	vulnInfos, err := o.scanVulnInfos(upgradablePacks)
 	if err != nil {
 		return nil, fmt.Errorf("Failed to scan unsecure packages. err: %s", err)
 	}
 
-	return cvePacksInfos, nil
+	return vulnInfos, nil
 }
 
 func (o *debian) ensureChangelogCache(current cache.Meta) error {
@@ -327,7 +330,7 @@ func (o *debian) fillCandidateVersion(before models.PackageInfoList) (filled []m
 	cmd := fmt.Sprintf("LANGUAGE=en_US.UTF-8 apt-cache policy %s", strings.Join(names, " "))
 	r := o.ssh(cmd, sudo)
 	if !r.isSuccess() {
-		return nil, fmt.Errorf("Failed to SSH: %s.", r)
+		return nil, fmt.Errorf("Failed to SSH: %s", r)
 	}
 	packChangelog := o.splitAptCachePolicy(r.Stdout)
 	for k, v := range packChangelog {
@@ -398,26 +401,26 @@ func (o *debian) parseAptGetUpgrade(stdout string) (upgradableNames []string, er
 	return
 }
 
-func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePacksList CvePacksList, err error) {
+func (o *debian) scanVulnInfos(upgradablePacks []models.PackageInfo) (models.VulnInfos, error) {
 	meta := cache.Meta{
 		Name:   o.getServerInfo().GetServerName(),
 		Distro: o.getServerInfo().Distro,
-		Packs:  unsecurePacks,
+		Packs:  upgradablePacks,
 	}
 
 	type strarray []string
 	resChan := make(chan struct {
 		models.PackageInfo
 		strarray
-	}, len(unsecurePacks))
-	errChan := make(chan error, len(unsecurePacks))
-	reqChan := make(chan models.PackageInfo, len(unsecurePacks))
+	}, len(upgradablePacks))
+	errChan := make(chan error, len(upgradablePacks))
+	reqChan := make(chan models.PackageInfo, len(upgradablePacks))
 	defer close(resChan)
 	defer close(errChan)
 	defer close(reqChan)
 
 	go func() {
-		for _, pack := range unsecurePacks {
+		for _, pack := range upgradablePacks {
 			reqChan <- pack
 		}
 	}()
@@ -425,7 +428,7 @@ func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePac
 	timeout := time.After(30 * 60 * time.Second)
 	concurrency := 10
 	tasks := util.GenWorkers(concurrency)
-	for range unsecurePacks {
+	for range upgradablePacks {
 		tasks <- func() {
 			select {
 			case pack := <-reqChan:
@@ -458,7 +461,7 @@ func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePac
 	// { CVE ID: [packageInfo] }
 	cvePackages := make(map[string][]models.PackageInfo)
 	errs := []error{}
-	for i := 0; i < len(unsecurePacks); i++ {
+	for i := 0; i < len(upgradablePacks); i++ {
 		select {
 		case pair := <-resChan:
 			pack := pair.PackageInfo
@@ -467,12 +470,11 @@ func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePac
 				cvePackages[cveID] = appendPackIfMissing(cvePackages[cveID], pack)
 			}
 			o.log.Infof("(%d/%d) Scanned %s-%s : %s",
-				i+1, len(unsecurePacks), pair.Name, pair.PackageInfo.Version, cveIDs)
+				i+1, len(upgradablePacks), pair.Name, pair.PackageInfo.Version, cveIDs)
 		case err := <-errChan:
 			errs = append(errs, err)
 		case <-timeout:
-			//TODO append to errs
-			return nil, fmt.Errorf("Timeout scanPackageCveIDs")
+			errs = append(errs, fmt.Errorf("Timeout scanPackageCveIDs"))
 		}
 	}
 	if 0 < len(errs) {
@@ -484,23 +486,14 @@ func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePac
 		cveIDs = append(cveIDs, k)
 	}
 	o.log.Debugf("%d Cves are found. cves: %v", len(cveIDs), cveIDs)
-
-	o.log.Info("Fetching CVE details...")
-	cveDetails, err := cveapi.CveClient.FetchCveDetails(cveIDs)
-	if err != nil {
-		return nil, err
-	}
-	o.log.Info("Done")
-
-	for _, detail := range cveDetails {
-		cvePacksList = append(cvePacksList, CvePacksInfo{
-			CveID:     detail.CveID,
-			CveDetail: detail,
-			Packs:     cvePackages[detail.CveID],
-			//  CvssScore: cinfo.CvssScore(conf.Lang),
+	var vinfos models.VulnInfos
+	for k, v := range cvePackages {
+		vinfos = append(vinfos, models.VulnInfo{
+			CveID:    k,
+			Packages: v,
 		})
 	}
-	return
+	return vinfos, nil
 }
 
 func (o *debian) getChangelogCache(meta cache.Meta, pack models.PackageInfo) string {
diff --git a/scan/freebsd.go b/scan/freebsd.go
index 2d904f17..0abee79e 100644
--- a/scan/freebsd.go
+++ b/scan/freebsd.go
@@ -22,7 +22,6 @@ import (
 	"strings"
 
 	"github.com/future-architect/vuls/config"
-	"github.com/future-architect/vuls/cveapi"
 	"github.com/future-architect/vuls/models"
 	"github.com/future-architect/vuls/util"
 )
@@ -87,12 +86,12 @@ func (o *bsd) scanPackages() error {
 	}
 	o.setPackages(packs)
 
-	var unsecurePacks []CvePacksInfo
-	if unsecurePacks, err = o.scanUnsecurePackages(); err != nil {
+	var vinfos []models.VulnInfo
+	if vinfos, err = o.scanUnsecurePackages(); err != nil {
 		o.log.Errorf("Failed to scan vulnerable packages")
 		return err
 	}
-	o.setUnsecurePackages(unsecurePacks)
+	o.setVulnInfos(vinfos)
 	return nil
 }
 
@@ -105,7 +104,7 @@ func (o *bsd) scanInstalledPackages() ([]models.PackageInfo, error) {
 	return o.parsePkgVersion(r.Stdout), nil
 }
 
-func (o *bsd) scanUnsecurePackages() (cvePacksList []CvePacksInfo, err error) {
+func (o *bsd) scanUnsecurePackages() (vulnInfos []models.VulnInfo, err error) {
 	const vulndbPath = "/tmp/vuln.db"
 	cmd := "rm -f " + vulndbPath
 	r := o.ssh(cmd, noSudo)
@@ -120,7 +119,7 @@ func (o *bsd) scanUnsecurePackages() (cvePacksList []CvePacksInfo, err error) {
 	}
 	if r.ExitStatus == 0 {
 		// no vulnerabilities
-		return []CvePacksInfo{}, nil
+		return []models.VulnInfo{}, nil
 	}
 
 	var packAdtRslt []pkgAuditResult
@@ -151,34 +150,22 @@ func (o *bsd) scanUnsecurePackages() (cvePacksList []CvePacksInfo, err error) {
 		}
 	}
 
-	cveIDs := []string{}
 	for k := range cveIDAdtMap {
-		cveIDs = append(cveIDs, k)
-	}
-
-	cveDetails, err := cveapi.CveClient.FetchCveDetails(cveIDs)
-	if err != nil {
-		return nil, err
-	}
-	o.log.Info("Done")
-
-	for _, d := range cveDetails {
 		packs := []models.PackageInfo{}
-		for _, r := range cveIDAdtMap[d.CveID] {
+		for _, r := range cveIDAdtMap[k] {
 			packs = append(packs, r.pack)
 		}
 
 		disAdvs := []models.DistroAdvisory{}
-		for _, r := range cveIDAdtMap[d.CveID] {
+		for _, r := range cveIDAdtMap[k] {
 			disAdvs = append(disAdvs, models.DistroAdvisory{
 				AdvisoryID: r.vulnIDCveIDs.vulnID,
 			})
 		}
 
-		cvePacksList = append(cvePacksList, CvePacksInfo{
-			CveID:            d.CveID,
-			CveDetail:        d,
-			Packs:            packs,
+		vulnInfos = append(vulnInfos, models.VulnInfo{
+			CveID:            k,
+			Packages:         packs,
 			DistroAdvisories: disAdvs,
 		})
 	}
diff --git a/scan/redhat.go b/scan/redhat.go
index 3dbfc585..7091b24d 100644
--- a/scan/redhat.go
+++ b/scan/redhat.go
@@ -26,7 +26,6 @@ import (
 	"time"
 
 	"github.com/future-architect/vuls/config"
-	"github.com/future-architect/vuls/cveapi"
 	"github.com/future-architect/vuls/models"
 	"github.com/future-architect/vuls/util"
 
@@ -189,12 +188,12 @@ func (o *redhat) scanPackages() error {
 	}
 	o.setPackages(packs)
 
-	var unsecurePacks []CvePacksInfo
-	if unsecurePacks, err = o.scanUnsecurePackages(); err != nil {
+	var vinfos []models.VulnInfo
+	if vinfos, err = o.scanVulnInfos(); err != nil {
 		o.log.Errorf("Failed to scan vulnerable packages")
 		return err
 	}
-	o.setUnsecurePackages(unsecurePacks)
+	o.setVulnInfos(vinfos)
 	return nil
 }
 
@@ -235,7 +234,7 @@ func (o *redhat) parseScannedPackagesLine(line string) (models.PackageInfo, erro
 	}, nil
 }
 
-func (o *redhat) scanUnsecurePackages() ([]CvePacksInfo, error) {
+func (o *redhat) scanVulnInfos() ([]models.VulnInfo, error) {
 	if o.Distro.Family != "centos" {
 		// Amazon, RHEL has yum updateinfo as default
 		// yum updateinfo can collenct vendor advisory information.
@@ -247,7 +246,7 @@ func (o *redhat) scanUnsecurePackages() ([]CvePacksInfo, error) {
 }
 
 // For CentOS
-func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (CvePacksList, error) {
+func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (models.VulnInfos, error) {
 	cmd := "LANGUAGE=en_US.UTF-8 yum --color=never %s check-update"
 	if o.getServerInfo().Enablerepo != "" {
 		cmd = fmt.Sprintf(cmd, "--enablerepo="+o.getServerInfo().Enablerepo)
@@ -268,19 +267,23 @@ func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (CvePacksList, error)
 	}
 	o.log.Debugf("%s", pp.Sprintf("%v", packInfoList))
 
+	// set candidate version info
+	o.Packages.MergeNewVersion(packInfoList)
+
 	// Collect CVE-IDs in changelog
 	type PackInfoCveIDs struct {
 		PackInfo models.PackageInfo
 		CveIDs   []string
 	}
 
-	// { packageName: changelog-lines }
-	var rpm2changelog map[string]*string
 	allChangelog, err := o.getAllChangelog(packInfoList)
 	if err != nil {
 		o.log.Errorf("Failed to getAllchangelog. err: %s", err)
 		return nil, err
 	}
+
+	// { packageName: changelog-lines }
+	var rpm2changelog map[string]*string
 	rpm2changelog, err = o.parseAllChangelog(allChangelog)
 	if err != nil {
 		return nil, fmt.Errorf("Failed to parseAllChangelog. err: %s", err)
@@ -337,39 +340,20 @@ func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (CvePacksList, error)
 	cveIDPackInfoMap := make(map[string][]models.PackageInfo)
 	for _, res := range results {
 		for _, cveID := range res.CveIDs {
-			//  packInfo, found := o.Packages.FindByName(res.Packname)
-			//  if !found {
-			//      return CvePacksList{}, fmt.Errorf(
-			//          "Faild to transform data structure: %v", res.Packname)
-			//  }
-			cveIDPackInfoMap[cveID] = append(cveIDPackInfoMap[cveID], res.PackInfo)
+			cveIDPackInfoMap[cveID] = append(
+				cveIDPackInfoMap[cveID], res.PackInfo)
 		}
 	}
 
-	var uniqueCveIDs []string
-	for cveID := range cveIDPackInfoMap {
-		uniqueCveIDs = append(uniqueCveIDs, cveID)
-	}
-
-	// cveIDs => []cve.CveInfo
-	o.log.Info("Fetching CVE details...")
-	cveDetails, err := cveapi.CveClient.FetchCveDetails(uniqueCveIDs)
-	if err != nil {
-		return nil, err
-	}
-	o.log.Info("Done")
-
-	cvePacksList := []CvePacksInfo{}
-	for _, detail := range cveDetails {
+	vinfos := []models.VulnInfo{}
+	for k, v := range cveIDPackInfoMap {
 		// Amazon, RHEL do not use this method, so VendorAdvisory do not set.
-		cvePacksList = append(cvePacksList, CvePacksInfo{
-			CveID:     detail.CveID,
-			CveDetail: detail,
-			Packs:     cveIDPackInfoMap[detail.CveID],
-			//  CvssScore: cinfo.CvssScore(conf.Lang),
+		vinfos = append(vinfos, models.VulnInfo{
+			CveID:    k,
+			Packages: v,
 		})
 	}
-	return cvePacksList, nil
+	return vinfos, nil
 }
 
 // parseYumCheckUpdateLines parse yum check-update to get package name, candidate version
@@ -579,11 +563,11 @@ type distroAdvisoryCveIDs struct {
 
 // Scaning unsecure packages using yum-plugin-security.
 // Amazon, RHEL
-func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (CvePacksList, error) {
+func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (models.VulnInfos, error) {
 	if o.Distro.Family == "centos" {
 		// CentOS has no security channel.
 		// So use yum check-update && parse changelog
-		return CvePacksList{}, fmt.Errorf(
+		return nil, fmt.Errorf(
 			"yum updateinfo is not suppported on CentOS")
 	}
 
@@ -615,6 +599,9 @@ func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (CvePacksList, err
 	}
 	o.log.Debugf("%s", pp.Sprintf("%v", updatable))
 
+	// set candidate version info
+	o.Packages.MergeNewVersion(updatable)
+
 	dict := map[string][]models.PackageInfo{}
 	for _, advIDPackNames := range advIDPackNamesList {
 		packInfoList := models.PackageInfoList{}
@@ -638,48 +625,41 @@ func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (CvePacksList, err
 	}
 	advisoryCveIDsList, err := o.parseYumUpdateinfo(r.Stdout)
 	if err != nil {
-		return CvePacksList{}, err
+		return nil, err
 	}
 	//  pp.Println(advisoryCveIDsList)
 
 	// All information collected.
-	// Convert to CvePacksList.
+	// Convert to VulnInfos.
 	o.log.Info("Fetching CVE details...")
-	result := CvePacksList{}
+	vinfos := models.VulnInfos{}
 	for _, advIDCveIDs := range advisoryCveIDsList {
-		cveDetails, err :=
-			cveapi.CveClient.FetchCveDetails(advIDCveIDs.CveIDs)
-		if err != nil {
-			return nil, err
-		}
-
-		for _, cveDetail := range cveDetails {
+		for _, cveID := range advIDCveIDs.CveIDs {
 			found := false
-			for i, p := range result {
-				if cveDetail.CveID == p.CveID {
+			for i, p := range vinfos {
+				if cveID == p.CveID {
 					advAppended := append(p.DistroAdvisories, advIDCveIDs.DistroAdvisory)
-					result[i].DistroAdvisories = advAppended
+					vinfos[i].DistroAdvisories = advAppended
 
 					packs := dict[advIDCveIDs.DistroAdvisory.AdvisoryID]
-					result[i].Packs = append(result[i].Packs, packs...)
+					vinfos[i].Packages = append(vinfos[i].Packages, packs...)
 					found = true
 					break
 				}
 			}
 
 			if !found {
-				cpinfo := CvePacksInfo{
-					CveID:            cveDetail.CveID,
-					CveDetail:        cveDetail,
+				cpinfo := models.VulnInfo{
+					CveID:            cveID,
 					DistroAdvisories: []models.DistroAdvisory{advIDCveIDs.DistroAdvisory},
-					Packs:            dict[advIDCveIDs.DistroAdvisory.AdvisoryID],
+					Packages:         dict[advIDCveIDs.DistroAdvisory.AdvisoryID],
 				}
-				result = append(result, cpinfo)
+				vinfos = append(vinfos, cpinfo)
 			}
+
 		}
 	}
-	o.log.Info("Done")
-	return result, nil
+	return vinfos, nil
 }
 
 var horizontalRulePattern = regexp.MustCompile(`^=+$`)
@@ -929,7 +909,6 @@ func (o *redhat) extractPackNameVerRel(nameVerRel string) (name, ver, rel string
 
 // parseYumUpdateinfoListAvailable collect AdvisorID(RHSA, ALAS), packages
 func (o *redhat) parseYumUpdateinfoListAvailable(stdout string) (advisoryIDPacksList, error) {
-
 	result := []advisoryIDPacks{}
 	lines := strings.Split(stdout, "\n")
 	for _, line := range lines {
diff --git a/scan/serverapi.go b/scan/serverapi.go
index eb8dbf32..d0111692 100644
--- a/scan/serverapi.go
+++ b/scan/serverapi.go
@@ -21,6 +21,7 @@ import (
 	"bufio"
 	"fmt"
 	"os"
+	"path/filepath"
 	"strings"
 	"time"
 
@@ -28,7 +29,7 @@ import (
 	"github.com/future-architect/vuls/cache"
 	"github.com/future-architect/vuls/config"
 	"github.com/future-architect/vuls/models"
-	cve "github.com/kotakanbe/go-cve-dictionary/models"
+	"github.com/future-architect/vuls/report"
 )
 
 // Log for localhsot
@@ -54,7 +55,6 @@ type osTypeInterface interface {
 
 	checkRequiredPackagesInstalled() error
 	scanPackages() error
-	scanVulnByCpeName() error
 	install() error
 	convertToModel() (models.ScanResult, error)
 
@@ -72,64 +72,15 @@ type osPackages struct {
 	Packages models.PackageInfoList
 
 	// unsecure packages
-	UnsecurePackages CvePacksList
+	VulnInfos models.VulnInfos
 }
 
 func (p *osPackages) setPackages(pi models.PackageInfoList) {
 	p.Packages = pi
 }
 
-func (p *osPackages) setUnsecurePackages(pi []CvePacksInfo) {
-	p.UnsecurePackages = pi
-}
-
-// CvePacksList have CvePacksInfo list, getter/setter, sortable methods.
-type CvePacksList []CvePacksInfo
-
-// CvePacksInfo hold the CVE information.
-type CvePacksInfo struct {
-	CveID            string
-	CveDetail        cve.CveDetail
-	Packs            models.PackageInfoList
-	DistroAdvisories []models.DistroAdvisory // for Aamazon, RHEL, FreeBSD
-	CpeNames         []string
-}
-
-// FindByCveID find by CVEID
-func (s CvePacksList) FindByCveID(cveID string) (pi CvePacksInfo, found bool) {
-	for _, p := range s {
-		if cveID == p.CveID {
-			return p, true
-		}
-	}
-	return CvePacksInfo{CveID: cveID}, false
-}
-
-// immutable
-func (s CvePacksList) set(cveID string, cvePacksInfo CvePacksInfo) CvePacksList {
-	for i, p := range s {
-		if cveID == p.CveID {
-			s[i] = cvePacksInfo
-			return s
-		}
-	}
-	return append(s, cvePacksInfo)
-}
-
-// Len implement Sort Interface
-func (s CvePacksList) Len() int {
-	return len(s)
-}
-
-// Swap implement Sort Interface
-func (s CvePacksList) Swap(i, j int) {
-	s[i], s[j] = s[j], s[i]
-}
-
-// Less implement Sort Interface
-func (s CvePacksList) Less(i, j int) bool {
-	return s[i].CveDetail.CvssScore(config.Conf.Lang) >
-		s[j].CveDetail.CvssScore(config.Conf.Lang)
+func (p *osPackages) setVulnInfos(vi []models.VulnInfo) {
+	p.VulnInfos = vi
 }
 
 func detectOS(c config.ServerInfo) (osType osTypeInterface) {
@@ -518,14 +469,15 @@ func Scan() []error {
 	}()
 
 	Log.Info("Scanning vulnerable OS packages...")
-	if errs := scanPackages(); errs != nil {
+	scannedAt := time.Now()
+	dir, err := ensureResultDir(scannedAt)
+	if err != nil {
+		return []error{err}
+	}
+	if errs := scanVulns(dir, scannedAt); errs != nil {
 		return errs
 	}
 
-	Log.Info("Scanning vulnerable software specified in the CPE...")
-	if errs := scanVulnByCpeName(); errs != nil {
-		return errs
-	}
 	return nil
 }
 
@@ -553,31 +505,67 @@ func checkRequiredPackagesInstalled() []error {
 	}, timeoutSec)
 }
 
-func scanPackages() []error {
+func scanVulns(jsonDir string, scannedAt time.Time) []error {
+	var results models.ScanResults
 	timeoutSec := 120 * 60
-	return parallelSSHExec(func(o osTypeInterface) error {
-		return o.scanPackages()
-	}, timeoutSec)
-
-}
-
-// scanVulnByCpeName search vulnerabilities that specified in config file.
-func scanVulnByCpeName() []error {
-	timeoutSec := 30 * 60
-	return parallelSSHExec(func(o osTypeInterface) error {
-		return o.scanVulnByCpeName()
-	}, timeoutSec)
-
-}
-
-// GetScanResults returns Scan Resutls
-func GetScanResults() (results models.ScanResults, err error) {
-	for _, s := range servers {
-		r, err := s.convertToModel()
-		if err != nil {
-			return results, fmt.Errorf("Failed converting to model: %s", err)
+	errs := parallelSSHExec(func(o osTypeInterface) error {
+		if err := o.scanPackages(); err != nil {
+			return err
 		}
+
+		r, err := o.convertToModel()
+		if err != nil {
+			return err
+		}
+		r.ScannedAt = scannedAt
 		results = append(results, r)
+
+		return nil
+	}, timeoutSec)
+
+	config.Conf.FormatJSON = true
+	ws := []report.ResultWriter{
+		report.LocalFileWriter{CurrentDir: jsonDir},
 	}
-	return
+	for _, w := range ws {
+		if err := w.Write(results...); err != nil {
+			return []error{
+				fmt.Errorf("Failed to write summary report: %s", err),
+			}
+		}
+	}
+	if errs != nil {
+		return errs
+	}
+
+	report.StdoutWriter{}.WriteScanSummary(results...)
+	return nil
+}
+
+func ensureResultDir(scannedAt time.Time) (currentDir string, err error) {
+	jsonDirName := scannedAt.Format(time.RFC3339)
+
+	resultsDir := config.Conf.ResultsDir
+	if len(resultsDir) == 0 {
+		wd, _ := os.Getwd()
+		resultsDir = filepath.Join(wd, "results")
+	}
+	jsonDir := filepath.Join(resultsDir, jsonDirName)
+	if err := os.MkdirAll(jsonDir, 0700); err != nil {
+		return "", fmt.Errorf("Failed to create dir: %s", err)
+	}
+
+	symlinkPath := filepath.Join(resultsDir, "current")
+	if _, err := os.Lstat(symlinkPath); err == nil {
+		if err := os.Remove(symlinkPath); err != nil {
+			return "", fmt.Errorf(
+				"Failed to remove symlink. path: %s, err: %s", symlinkPath, err)
+		}
+	}
+
+	if err := os.Symlink(jsonDir, symlinkPath); err != nil {
+		return "", fmt.Errorf(
+			"Failed to create symlink: path: %s, err: %s", symlinkPath, err)
+	}
+	return jsonDir, nil
 }
diff --git a/scan/serverapi_test.go b/scan/serverapi_test.go
index a9b5b64e..040485ba 100644
--- a/scan/serverapi_test.go
+++ b/scan/serverapi_test.go
@@ -1,158 +1 @@
 package scan
-
-import (
-	"github.com/future-architect/vuls/config"
-	"github.com/future-architect/vuls/models"
-	cve "github.com/kotakanbe/go-cve-dictionary/models"
-	"reflect"
-	"testing"
-)
-
-func TestPackageCveInfosSetGet(t *testing.T) {
-	var test = struct {
-		in  []string
-		out []string
-	}{
-		[]string{
-			"CVE1",
-			"CVE2",
-			"CVE3",
-			"CVE1",
-			"CVE1",
-			"CVE2",
-			"CVE3",
-		},
-		[]string{
-			"CVE1",
-			"CVE2",
-			"CVE3",
-		},
-	}
-
-	//  var ps packageCveInfos
-	var ps CvePacksList
-	for _, cid := range test.in {
-		ps = ps.set(cid, CvePacksInfo{CveID: cid})
-	}
-
-	if len(test.out) != len(ps) {
-		t.Errorf("length: expected %d, actual %d", len(test.out), len(ps))
-	}
-
-	for i, expectedCid := range test.out {
-		if expectedCid != ps[i].CveID {
-			t.Errorf("expected %s, actual %s", expectedCid, ps[i].CveID)
-		}
-	}
-	for _, cid := range test.in {
-		p, _ := ps.FindByCveID(cid)
-		if p.CveID != cid {
-			t.Errorf("expected %s, actual %s", cid, p.CveID)
-		}
-	}
-}
-
-func TestGetScanResults(t *testing.T) {
-	// setup servers
-	c := config.ServerInfo{
-		ServerName: "ubuntu",
-	}
-	deb1 := newDebian(c)
-	deb2 := newDebian(c)
-
-	cpis1 := []CvePacksInfo{
-		{
-			CveID:     "CVE1",
-			CveDetail: cve.CveDetail{CveID: "CVE1"},
-			Packs: []models.PackageInfo{
-				{Name: "mysql-client-5.5"},
-				{Name: "mysql-server-5.5"},
-				{Name: "mysql-common-5.5"},
-			},
-		},
-		{
-			CveID:     "CVE2",
-			CveDetail: cve.CveDetail{CveID: "CVE2"},
-			Packs: []models.PackageInfo{
-				{Name: "mysql-common-5.5"},
-				{Name: "mysql-server-5.5"},
-				{Name: "mysql-client-5.5"},
-			},
-		},
-	}
-	cpis2 := []CvePacksInfo{
-		{
-			CveID:     "CVE3",
-			CveDetail: cve.CveDetail{CveID: "CVE3"},
-			Packs: []models.PackageInfo{
-				{Name: "libcurl3"},
-				{Name: "curl"},
-			},
-		},
-		{
-			CveID:     "CVE4",
-			CveDetail: cve.CveDetail{CveID: "CVE4"},
-			Packs: []models.PackageInfo{
-				{Name: "bind9"},
-				{Name: "libdns100"},
-			},
-		},
-	}
-	deb1.setUnsecurePackages(cpis1)
-	servers = append(servers, deb1)
-
-	deb2.setUnsecurePackages(cpis2)
-	servers = append(servers, deb2)
-
-	// prepare expected data
-	expectedUnKnownPackages := []map[string][]models.PackageInfo{
-		{
-			"CVE1": {
-				{Name: "mysql-client-5.5"},
-				{Name: "mysql-common-5.5"},
-				{Name: "mysql-server-5.5"},
-			},
-		},
-		{
-			"CVE2": {
-				{Name: "mysql-client-5.5"},
-				{Name: "mysql-common-5.5"},
-				{Name: "mysql-server-5.5"},
-			},
-		},
-		{
-			"CVE3": {
-				{Name: "curl"},
-				{Name: "libcurl3"},
-			},
-		},
-		{
-			"CVE4": {
-				{Name: "bind9"},
-				{Name: "libdns100"},
-			},
-		},
-	}
-
-	// check scanResults
-	scanResults, _ := GetScanResults()
-	if len(scanResults) != 2 {
-		t.Errorf("length of scanResults should be 2")
-	}
-	for i, result := range scanResults {
-		if result.ServerName != "ubuntu" {
-			t.Errorf("expected ubuntu, actual %s", result.ServerName)
-		}
-
-		unKnownCves := result.UnknownCves
-		if len(unKnownCves) != 2 {
-			t.Errorf("length of unKnownCves should be 2")
-		}
-		for j, unKnownCve := range unKnownCves {
-			expected := expectedUnKnownPackages[i*2+j][unKnownCve.CveDetail.CveID]
-			if !reflect.DeepEqual(expected, unKnownCve.Packages) {
-				t.Errorf("expected %v, actual %v", expected, unKnownCve.Packages)
-			}
-		}
-	}
-}
diff --git a/setup/docker/README.md b/setup/docker/README.md
index d72aae70..8ac1144b 100644
--- a/setup/docker/README.md
+++ b/setup/docker/README.md
@@ -149,12 +149,24 @@ $ docker run --rm -it \
     -v /etc/localtime:/etc/localtime:ro \
     -e "TZ=Asia/Tokyo" \
     vuls/vuls scan \
-    -cve-dictionary-dbpath=/vuls/cve.sqlite3 \
-    -report-json \
     -config=./config.toml # path to config.toml in docker
 ```
 
-## Step5. vulsrepo
+## Step5. Report
+
+```console
+$ docker run --rm -it \
+    -v ~/.ssh:/root/.ssh:ro \
+    -v $PWD:/vuls \
+    -v $PWD/vuls-log:/var/log/vuls \
+    -v /etc/localtime:/etc/localtime:ro \
+    vuls/vuls report \
+    -cvedb-path=/vuls/cve.sqlite3 \
+    -format-short-text \
+    -config=./config.toml # path to config.toml in docker
+```
+
+## Step6. vulsrepo
 
 ```console
 $docker run -dt \
diff --git a/setup/docker/vuls/latest/README.md b/setup/docker/vuls/latest/README.md
index 0fe86285..370b09b1 100644
--- a/setup/docker/vuls/latest/README.md
+++ b/setup/docker/vuls/latest/README.md
@@ -83,9 +83,21 @@ $ docker run --rm -it \
     -v $PWD/vuls-log:/var/log/vuls \
     -v /etc/localtime:/etc/localtime:ro \
     vuls/vuls scan \
-    -cve-dictionary-dbpath=/vuls/cve.sqlite3 \
-    -config=./config.toml \ # path to config.toml in docker
-    -report-json 
+    -config=./config.toml # path to config.toml in docker
+```
+
+## Report
+
+```console
+$ docker run --rm -it \
+    -v ~/.ssh:/root/.ssh:ro \
+    -v $PWD:/vuls \
+    -v $PWD/vuls-log:/var/log/vuls \
+    -v /etc/localtime:/etc/localtime:ro \
+    vuls/vuls report \
+    -cvedb-path=/vuls/cve.sqlite3 \
+    -format-short-text \
+    -config=./config.toml # path to config.toml in docker
 ```
 
 ## tui
@@ -94,7 +106,8 @@ $ docker run --rm -it \
 $ docker run --rm -it \
     -v $PWD:/vuls \
     -v $PWD/vuls-log:/var/log/vuls \
-    vuls/vuls tui 
+    vuls/vuls tui \
+    -cvedb-path=/vuls/cve.sqlite3 
 ```
 
 ## vulsrepo