diff --git a/.docker-test b/.docker-test
deleted file mode 100644
index d36fad8..0000000
--- a/.docker-test
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/bin/bash
-
-ruby_version=${1:-2.4.2}
-
-if ! rvm use ruby-${ruby_version} &>/dev/null ; then
-  echo "The ruby version '${ruby_version}' doesn't exist!"
-  echo "Available versions are:"
-  rvm list rubies strings | cut -d '-' -f2
-  exit 2
-fi
-
-echo '# ---------------------------------'
-echo "# Use ruby version: ${ruby_version}"
-echo '# ---------------------------------'
-
-cp -r /mpw ~/mpw
-cd ~/mpw
-gem install bundler --no-ri --no-rdoc
-bundle install
-gem build mpw.gemspec
-gem install mpw-$(cat VERSION).gem
-cp -a /dev/urandom /dev/random
-
-rubocop
-ruby ./test/init.rb
-ruby ./test/test_config.rb
-ruby ./test/test_item.rb
-ruby ./test/test_mpw.rb
-ruby ./test/test_translate.rb
-ruby ./test/init.rb
-ruby ./test/test_cli.rb
-ruby ./test/test_import.rb
diff --git a/.gitignore b/.gitignore
index afd83c3..b844b14 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1 @@
 Gemfile.lock
-*.gem
-.yardoc
-doc
diff --git a/.rubocop.yml b/.rubocop.yml
deleted file mode 100644
index 56d1540..0000000
--- a/.rubocop.yml
+++ /dev/null
@@ -1,38 +0,0 @@
-
-AllCops:
-  Exclude:
-    - db/**/*
-    - config/**/*
-    - Vagrantfile
-  TargetRubyVersion: 2.3
-
-Naming/AccessorMethodName:
-  Enabled: false
-
-Lint/RescueWithoutErrorClass:
-  Enabled: false
-
-Metrics/LineLength:
-  Max: 120
-Metrics/CyclomaticComplexity:
-  Enabled: false
-Metrics/PerceivedComplexity:
-  Enabled: false
-Metrics/MethodLength:
-  Enabled: false
-Metrics/BlockLength:
-  Enabled: false
-Metrics/ClassLength:
-  Enabled: false
-Metrics/AbcSize:
-  Enabled: false
-
-Style/NumericLiteralPrefix:
-  Enabled: false
-Style/FrozenStringLiteralComment:
-  Enabled: false
-Style/CommandLiteral:
-  Enabled: true
-  EnforcedStyle: percent_x
-Style/Documentation:
-  Enabled: false
diff --git a/.travis.yml b/.travis.yml
index 14d696a..141eab1 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,23 +1,13 @@
 language: ruby
-dist: precise
 rvm:
-  - 2.4.2
-  - 2.3.5
-  - 2.2.8
+  - 2.3.1
+  - 2.2.5
   - 2.1.10
 install:
-  - sudo cp -a /dev/urandom /dev/random
-  - sudo apt-get purge -y gnupg-agent gnupg2
   - bundle install
+  - gem install 'test-unit'
+  - echo 9999 > VERSION
   - gem build mpw.gemspec
-  - gem install mpw-$(cat VERSION).gem
+  - gem install mpw-9999.gem
 script:
-  - rubocop
-  - ruby ./test/init.rb
-  - ruby ./test/test_config.rb
-  - ruby ./test/test_item.rb
-  - ruby ./test/test_mpw.rb
-  - ruby ./test/test_translate.rb
-  - ruby ./test/init.rb
-  - ruby ./test/test_cli.rb
-  - ruby ./test/test_import.rb
+  - ruby ./test/tests.rb 
diff --git a/CHANGELOG b/CHANGELOG
new file mode 100644
index 0000000..261d083
--- /dev/null
+++ b/CHANGELOG
@@ -0,0 +1,43 @@
+= CHANGELOG =
+== v4.0.0 (beta) ==
+
+* new interface with a table
+* new command line interface
+* use text editor for add or update an item
+* several bugs fix
+
+== v3.2.1 ==
+
+* fix bug when add a new item
+
+== v3.2.0 ==
+
+* add support OTP
+* fix bug in synchronize
+* improve interface
+
+== v3.1.0 ==
+
+* add clipboard
+* can change gpg version
+* minor change in interface
+* several bugs fix
+
+== v3.0.0 ==
+
+* new storage format
+* new share system
+* remove MPW server
+
+== v2.0.0 ==
+
+* change format csv to yaml
+* easy install with gem
+* add sync with ftp and ssh
+* many improvement
+
+== v1.1.0 ==
+
+* Add sync with MPW Server
+* Add MPW Server
+* Fix minors bugs
diff --git a/CHANGELOG.md b/CHANGELOG.md
deleted file mode 100644
index 199b863..0000000
--- a/CHANGELOG.md
+++ /dev/null
@@ -1,98 +0,0 @@
-# CHANGELOG
-## v4.2.2 (2017-08-15)
-
-  * minor improvements in the interface
-
-## v4.2.1 (2017-07-30)
-
-  * fix bug in otp generator
-
-## v4.2.0 (2017-06-06)
-
- * feat: improve the interface
- * feat: add copy url
- * feat: add unit tests for cli
- * feat: comment the code with yarn syntax
- * fix several bugs
- * fix translations
-
-## v4.1.1 (2017-05-03)
-
- * fix bug in init
-
-## v4.1.0 (2017-04-22)
-
- * feat: add options to update or add an item in command line
- * feat: print config
- * feat: add a specific path for a wallet
- * feat: add rubocop to fix syntax
- * fix: pinentry mode with gpg >= 2.1
- * remove SSH and FTP synchronization
-
-## v4.0.0 (2017-03-09)
-
- * feature: set default wallet
- * add option for generate a random password when you update an item
- * fix encryption when you share an existing wallet
- * several bugs fix
-
-## v4.0.0-beta1 (2017-02-16)
-
- * add manage share key with new interface
-
-## v4.0.0-beta (2016-11-11)
-
- * new interface with a table
- * new command line interface
- * use text editor for add or update an item
- * fix generate gpg key with RSA
- * several bugs fix
- * add unit tests
-
-## v3.2.1 (2016-08-06)
-
- * fix bug when add a new item
-
-## v3.2.0 (2016-08-03)
-
- * add support OTP
- * fix bug in synchronize
- * improve interface
-
-## v3.1.0 (2016-07-09)
-
- * add clipboard
- * can change gpg version
- * minor change in interface
- * several bugs fix
-
-## v3.0.0 (2016-07-05)
-
- * new storage format
- * new share system
- * remove MPW server
-
-## v2.0.3 (2015-09-27)
-
- * add no-sync option
-
-## v2.0.1 (2015-06-23)
-
- * fix mpw-ssh
-
-## v2.0.0 (2015-06-22)
-
- * change format csv to yaml
- * easy install with gem
- * add sync with ftp and ssh
- * many improvement
-
-## v1.1.0 (2014-01-28)
-
- * Add sync with MPW Server
- * Add MPW Server
- * Fix minors bugs
-
-## v1.0.0 (2014-01-15)
-
-  * first release
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index 6654780..0000000
--- a/Dockerfile
+++ /dev/null
@@ -1,16 +0,0 @@
-FROM debian:stretch
-MAINTAINER Adrien Waksberg "mpw@yae.im"
-
-RUN apt update
-RUN apt dist-upgrade -y
-
-RUN apt install -y procps gnupg1 curl git
-RUN ln -snvf /usr/bin/gpg1 /usr/bin/gpg
-RUN gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
-RUN curl -sSL https://get.rvm.io | bash -s stable
-RUN echo 'source "/usr/local/rvm/scripts/rvm"' >> /etc/bash.bashrc
-
-RUN /bin/bash -l -c "rvm install 2.4.2"
-RUN /bin/bash -l -c "rvm install 2.3.5"
-RUN /bin/bash -l -c "rvm install 2.2.8"
-RUN /bin/bash -l -c "rvm install 2.1.10"
diff --git a/Gemfile b/Gemfile
index a76b2c1..a588d17 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,14 +1,10 @@
 source 'https://rubygems.org'
-gem 'clipboard', '~> 1.1', '>= 1.1.1'
-gem 'colorize',  '~> 0.8', '>= 0.8.1'
-gem 'gpgme',     '~> 2.0', '>= 2.0.14'
-gem 'highline',  '~> 1.7', '>= 1.7.8'
-gem 'i18n',      '~> 0.9', '>= 0.9.1'
-gem 'locale',    '~> 2.1', '>= 2.1.2'
-gem 'rotp',      '~> 3.3', '>= 3.3.0'
-
-group :development do
-  gem 'rubocop', '0.50.0'
-  gem 'test-unit'
-  gem 'yard'
-end
+gem "i18n",      "~> 0.7", ">= 0.7.0"
+gem "gpgme",     "~> 2.0", ">= 2.0.12"
+gem "highline",  "~> 1.7", ">= 1.7.8"
+gem "locale",    "~> 2.1", ">= 2.1.2"
+gem "colorize",  "~> 0.8", ">= 0.8.1"
+gem "net-ssh",   "~> 3.2", ">= 3.2.0"
+gem "net-sftp",  "~> 2.1", ">= 2.1.2"
+gem "clipboard", "~> 1.1", ">= 1.1.1"
+gem "rotp",      "~> 3.1", ">= 3.1.0"
diff --git a/LICENSE b/LICENSE
index 0c5ea8e..d159169 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,201 +1,339 @@
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
 
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
 
-   1. Definitions.
+                            Preamble
 
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
 
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
 
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
 
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
 
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
 
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
 
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
 
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
+  The precise terms and conditions for copying, distribution and
+modification follow.
 
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
 
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
 
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
 
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
 
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
 
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
 
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
 
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
 
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
 
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
 
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
 
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
 
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
 
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
 
-   END OF TERMS AND CONDITIONS
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
 
-   APPENDIX: How to apply the Apache License to your work.
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
 
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "{}"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
 
-   Copyright 2017 Adrien Waksberg
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
 
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
 
-       http://www.apache.org/licenses/LICENSE-2.0
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
 
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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 2 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, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/README.md b/README.md
deleted file mode 100644
index be9e7cb..0000000
--- a/README.md
+++ /dev/null
@@ -1,195 +0,0 @@
-# MPW: Manage your passwords!
-[![Version](https://img.shields.io/badge/latest_version-4.2.2-green.svg)](https://github.com/nishiki/manage-password/releases)
-[![Build Status](https://travis-ci.org/nishiki/manage-password.svg?branch=master)](https://travis-ci.org/nishiki/manage-password)
-[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://github.com/nishiki/manage-password/blob/master/LICENSE)
-
-mpw is a little software which stores your passwords in [GnuPG](http://www.gnupg.org/) encrypted files.
-
-## Features
-
- * generate random password
- * generate OTP code
- * copy your login, password or otp in clipboard
- * manage many wallets
- * share a wallet with others GPG keys
-
-## Install
-
-On debian or ubuntu:
-```
-apt install ruby ruby-dev xclip
-gem install mpw
-```
-
-## How to use
-### First steps
-
-Initialize your first wallet:
-```
-mpw config --init user@host.com
-```
-
-Add your first item:
-```
-mpw add --host assurance.com --port 443 --user user_2132 --protocol https --random
-mpw add --host fric.com --user 230403 --otp-code 23434113 --protocol https --comment 'I love my bank' --random
-```
-
-And list your items:
-```
-mpw list
-```
-or search an item with
-```
-mpw list --pattern love
-mpw list --group bank
-```
-
-Output:
-```
-Assurance
- ==========================================================================
-  ID | Host                        | User        | OTP | Comment          
- ==========================================================================
-  1  | https://assurance.com:443   | user_2132   |     |                  
-
-Bank
- ==========================================================================
-  ID | Host                        | User        | OTP | Comment          
- ==========================================================================
-  3  | https://fric.com            | 230403      |  X  | I love my bank   
-```
-
-Copy a password, login or OTP code:
-```
-mpw copy -p assurance.com
-```
-
-Update an item:
-```
-mpw update -p assurance.com
-```
-
-Delete an item:
-```
-mpw delete -p assurance.com
-```
-
-### Manage wallets
-
-List all available wallets:
-```
-mpw wallet
-```
-
-List all GPG keys in wallet:
-```
-mpw wallet --list-keys [--wallet NAME]
-```
-
-Share with an other GPG key:
-```
-mpw wallet --add-gpg-key test42@localhost.com
- or
-mpw wallet --add-gpg-key /path/to/file
-```
-
-Remove a GPG key:
-```
-mpw wallet --delete-gpg-key test42@localhost.com
-```
-
-### Export and import data
-
-You can export your data in yaml file with your passwords in clear text:
-```
-mpw export --file export.yml
-```
-
-Import data from an yaml file:
-```
-mpw import --file import.yml
-```
-
-Example yaml file for mpw:
-
-```
----
-1:
-  host: fric.com
-  user: 230403
-  group: Bank
-  password: 5XdiTQOubRDw9B0aJoMlcEyL
-  protocol: https
-  port:
-  otp_key: 330223432
-  comment: I love my bank
-2:
-  host: assurance.com
-  user: user_2132
-  group: Assurance
-  password: DMyK6B3v4bWO52VzU7aTHIem
-  protocol: https
-  port: 443
-  otp_key:
-  comment:
-```
-
-### Config
-
-Print the current config
-```
-mpw config
-```
-
-Output:
-
-```
-Configuration
- ==============================================
-  lang             | fr
-  gpg_key          | mpw@yae.im
-  default_wallet   |
-  config_dir       | /home/mpw/.config/mpw
-  pinmode          | true
-  gpg_exe          |
-  path_wallet_test | /tmp/test.mpw
-  password_numeric | true
-  password_alpha   | true
-  password_special | false
-  password_length  | 16
-
-```
-
-## Development
-
-Don't run the tests on your local machine, you risk to lost your datas.
-
-### Test on local machine with docker
-
-  * install [docker](https://docs.docker.com/engine/installation/)
-  * run the tests
-
-```
-docker run -v $(pwd):/mpw:ro -it nishiki/ruby:stretch /bin/bash -l /mpw/.docker-test
-```
-
-## License
-
-```
-* Author:: Adrien Waksberg <mpw@yae.im>
-
-Copyright (c) 2013-2017 Adrien Waksberg
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-```
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..17725d2
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,54 @@
+MPW: Manage your passwords!
+*******************************************************
+|Version| |Build Status| |License|
+
+mpw is a little software which stores your passwords in `GnuPG <http://www.gnupg.org/>`_ encrypted files.
+
+Features
+========
+
+* generate OTP code
+* synchronize your passwords with SSH or FTP.
+* copy your login, password or otp in clipboard
+
+Install
+=======
+
+On debian or ubuntu::
+
+	apt install ruby ruby-dev xclip
+	gem install mpw
+
+
+How to use
+==========
+
+A simple mpw usage::
+
+	mpw config --init user@host.com
+	mpw add
+	mpw copy
+	mpw add
+	mpw list
+
+Output::
+
+	Bank
+	 ==============================================================================
+	  ID | Host          | User      | Protocol | Port | OTP | Comment                
+	 ==============================================================================
+	  1  | bank.com      | 1234456   | https    |      |  X  |                        
+
+	Linux
+	 ==============================================================================
+	  ID | Host          | User      | Protocol | Port | OTP | Comment                
+	 ==============================================================================
+	  2  | linuxfr.org   | example   | https    |      |     | Da Linux French Site
+
+
+.. |Version| image:: https://img.shields.io/badge/latest_version-4.0.0--beta-yellow.svg
+   :target: https://github.com/nishiki/manage-password/releases
+.. |License| image:: https://img.shields.io/badge/license-GPL--2.0-blue.svg
+   :target: https://github.com/nishiki/manage-password/blob/master/LICENSE
+.. |Build Status| image:: https://travis-ci.org/nishiki/manage-password.svg?branch=master
+   :target: https://travis-ci.org/nishiki/manage-password
diff --git a/VERSION b/VERSION
index af8c8ec..c0de572 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-4.2.2
+4.0.0-beta
diff --git a/bin/mpw b/bin/mpw
index 11bc726..8e578f8 100755
--- a/bin/mpw
+++ b/bin/mpw
@@ -1,30 +1,26 @@
-#!/usr/bin/env ruby
-#
-# Copyright:: 2013, Adrien Waksberg
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
+#!/usr/bin/ruby
+# MPW is a software to crypt and manage your passwords
+# Copyright (C) 2016  Adrien Waksberg <mpw@yae.im>
+# 
+# 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 2
+# 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
-$LOAD_PATH << File.expand_path('../../lib', __FILE__)
+$: << File.expand_path('../../lib', __FILE__)
 
 require 'locale'
 require 'set'
 require 'i18n'
-require 'colorize'
 
 # --------------------------------------------------------- #
 # Set local
@@ -33,7 +29,7 @@ require 'colorize'
 lang = Locale::Tag.parse(ENV['LANG']).to_simple.to_s[0..1]
 
 if defined?(I18n.enforce_available_locales)
-  I18n.enforce_available_locales = true
+	I18n.enforce_available_locales = true
 end
 
 I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
@@ -48,25 +44,21 @@ I18n.locale         = lang.to_sym
 bin_dir = File.dirname(__FILE__)
 command = "#{bin_dir}/mpw-#{ARGV[0]}"
 
-if Dir.glob("#{bin_dir}/mpw-*").include?(command.to_s)
-  begin
-    Kernel.load(command)
-  rescue OptionParser::ParseError => e
-    puts "#{I18n.t('display.error')}: #{e}".red
-  end
+if Dir.glob("#{bin_dir}/mpw-*").include?("#{command}")
+	Kernel.load(command)
 else
-  puts "#{I18n.t('option.usage')}: mpw COMMAND [options]\n\n"
-  puts 'Commands:'
-  puts "  add       #{I18n.t('command.add')}"
-  puts "  config    #{I18n.t('command.config')}"
-  puts "  copy      #{I18n.t('command.copy')}"
-  puts "  delete    #{I18n.t('command.delete')}"
-  puts "  export    #{I18n.t('command.export')}"
-  puts "  genpwd    #{I18n.t('command.genpwd')}"
-  puts "  import    #{I18n.t('command.import')}"
-  puts "  list      #{I18n.t('command.list')}"
-  puts "  update    #{I18n.t('command.update')}"
-  puts "  wallet    #{I18n.t('command.wallet')}"
+	puts "#{I18n.t('option.usage')}: mpw COMMAND [options]\n\n"
+	puts 'Commands:'
+	puts "  add       #{I18n.t('command.add')}"
+	puts "  config    #{I18n.t('command.config')}"
+	puts "  copy      #{I18n.t('command.copy')}"
+	puts "  delete    #{I18n.t('command.delete')}"
+	puts "  export    #{I18n.t('command.export')}"
+	puts "  genpwd    #{I18n.t('command.genpwd')}"
+	puts "  import    #{I18n.t('command.import')}"
+	puts "  list      #{I18n.t('command.list')}"
+	puts "  update    #{I18n.t('command.update')}"
+	puts "  wallet    #{I18n.t('command.wallet')}"
 
-  exit 3
+	exit 3
 end
diff --git a/bin/mpw-add b/bin/mpw-add
index e08caae..5322cc1 100644
--- a/bin/mpw-add
+++ b/bin/mpw-add
@@ -1,22 +1,20 @@
-#
-# Copyright:: 2013, Adrien Waksberg
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
+#!/usr/bin/ruby
+# MPW is a software to crypt and manage your passwords
+# Copyright (C) 2016  Adrien Waksberg <mpw@yae.im>
+# 
+# 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 2
+# 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 require 'optparse'
 require 'mpw/config'
@@ -26,59 +24,38 @@ require 'mpw/cli'
 # Options
 # --------------------------------------------------------- #
 
-values                = {}
-options               = {}
-options[:text_editor] = true
+options        = {}
+options[:sync] = true
 
 OptionParser.new do |opts|
-  opts.banner = "#{I18n.t('option.usage')}: mpw add [options]"
+	opts.banner = "#{I18n.t('option.usage')}: mpw add [options]"
 
-  opts.on('-c', '--config PATH', I18n.t('option.config')) do |config|
-    options[:config] = config
-  end
+	opts.on('-c', '--config PATH', I18n.t('option.config')) do |config|
+		options[:config] = config
+	end
 
-  opts.on('-C', '--comment COMMENT', I18n.t('option.comment')) do |comment|
-    values[:comment] = comment
-  end
+	opts.on('-h', '--help', I18n.t('option.help')) do
+		puts opts
+		exit 0
+	end
 
-  opts.on('-G', '--group NAME', I18n.t('option.new_group')) do |group|
-    values[:group] = group
-  end
+	opts.on('-n', '--no-sync', I18n.t('option.no_sync')) do
+		options[:sync] = false
+	end
 
-  opts.on('-h', '--help', I18n.t('option.help')) do
-    puts opts
-    exit 0
-  end
+	opts.on('-r', '--random', I18n.t('option.random_password')) do
+		options[:password] = true
+	end
 
-  opts.on('-o', '--otp-code CODE', I18n.t('option.otp_code')) do |otp|
-    values[:otp_key] = otp
-  end
-
-  opts.on('-r', '--random', I18n.t('option.random_password')) do
-    options[:password] = true
-  end
-
-  opts.on('-t', '--text-editor', I18n.t('option.text_editor')) do
-    options[:text_editor] = true
-  end
-
-  opts.on('-u', '--url URL', I18n.t('option.url')) do |url|
-    values[:url] = url
-  end
-
-  opts.on('-U', '--user USER', I18n.t('option.user')) do |user|
-    values[:user] = user
-  end
-
-  opts.on('-w', '--wallet NAME', I18n.t('option.wallet')) do |wallet|
-    options[:wallet] = wallet
-  end
+	opts.on('-w', '--wallet NAME', I18n.t('option.wallet')) do |wallet|
+		options[:wallet] = wallet
+	end
 end.parse!
 
 config = MPW::Config.new(options[:config])
-cli    = MPW::Cli.new(config)
+cli    = MPW::Cli.new(config, options[:sync])
 
 cli.load_config
 cli.get_wallet(options[:wallet])
 cli.decrypt
-cli.add(options[:password], options[:text_editor], values)
+cli.add(options[:password])
diff --git a/bin/mpw-config b/bin/mpw-config
index 2ac0f77..120da5b 100644
--- a/bin/mpw-config
+++ b/bin/mpw-config
@@ -1,22 +1,20 @@
-#
-# Copyright:: 2013, Adrien Waksberg
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
+#!/usr/bin/ruby
+# MPW is a software to crypt and manage your passwords
+# Copyright (C) 2016  Adrien Waksberg <mpw@yae.im>
+# 
+# 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 2
+# 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 require 'optparse'
 require 'mpw/config'
@@ -30,92 +28,48 @@ options = {}
 values  = {}
 
 OptionParser.new do |opts|
-  opts.banner = "#{I18n.t('option.usage')}: mpw config [options]"
+	opts.banner = "#{I18n.t('option.usage')}: mpw config [options]"
 
-  opts.on('-c', '--config PATH', I18n.t('option.config')) do |config|
-    options[:config] = config
-  end
+	opts.on('-c', '--config PATH', I18n.t('option.config')) do |config|
+		options[:config] = config
+	end
 
-  opts.on('-d', '--default-wallet NAME', I18n.t('option.default_wallet')) do |default_wallet|
-    values[:default_wallet] = default_wallet
-  end
+	opts.on('-g', '--gpg-exe PATH', I18n.t('option.gpg_exe')) do |gpg_exe|
+		values[:gpg_exe] = gpg_exe
+	end
 
-  opts.on('-g', '--gpg-exe PATH', I18n.t('option.gpg_exe')) do |gpg_exe|
-    values[:gpg_exe] = gpg_exe
-  end
+	opts.on('-h', '--help', I18n.t('option.help')) do
+		puts opts
+		exit 0
+	end
 
-  opts.on('-h', '--help', I18n.t('option.help')) do
-    puts opts
-    exit 0
-  end
+	opts.on('-i', '--init GPG_KEY', I18n.t('option.init')) do |gpg_key|
+		options[:init]   = true
+		values[:gpg_key] = gpg_key
+	end
 
-  opts.on('-i', '--init GPG_KEY', I18n.t('option.init')) do |gpg_key|
-    options[:init]   = true
-    values[:gpg_key] = gpg_key
-  end
+	opts.on('-k', '--key GPG_KEY', I18n.t('option.gpg_key')) do |gpg_key|
+		values[:gpg_key] = gpg_key
+	end
 
-  opts.on('-k', '--key GPG_KEY', I18n.t('option.gpg_key')) do |gpg_key|
-    values[:gpg_key] = gpg_key
-  end
+	opts.on('-l', '--lang LANG', I18n.t('option.lang')) do |lang|
+		values[:lang] = lang
+	end
 
-  opts.on('-L', '--lang LANG', I18n.t('option.lang')) do |lang|
-    values[:lang] = lang
-  end
-
-  opts.on('-P', '--enable-pinmode', I18n.t('option.pinmode')) do
-    values[:pinmode] = true
-  end
-
-  opts.on('-p', '--disable-pinmode', I18n.t('option.disable_pinmode')) do
-    values[:pinmode] = false
-  end
-
-  opts.on('-w', '--wallet-dir PATH', I18n.t('option.wallet_dir')) do |wallet_dir|
-    values[:wallet_dir] = wallet_dir
-  end
-
-  opts.on('-l', '--length NUMBER', I18n.t('option.length')) do |length|
-    values[:pwd_length] = length.to_i
-  end
-
-  opts.on('-n', '--numeric', I18n.t('option.numeric')) do
-    values[:pwd_numeric] = true
-  end
-
-  opts.on('-N', '--disable-numeric', I18n.t('option.disable_numeric')) do
-    values[:pwd_numeric] = false
-  end
-
-  opts.on('-s', '--special-chars', I18n.t('option.special_chars')) do
-    values[:pwd_special] = true
-  end
-
-  opts.on('-S', '--disable-special-chars', I18n.t('option.special_chars')) do
-    values[:pwd_special] = false
-  end
-
-  opts.on('-a', '--alpha', I18n.t('option.alpha')) do
-    values[:pwd_alpha] = true
-  end
-
-  opts.on('-A', '--disable-alpha', I18n.t('option.disable_alpha')) do
-    values[:pwd_alpha] = false
-  end
+	opts.on('-w', '--wallet-dir PATH', I18n.t('option.wallet_dir')) do |wallet_dir|
+		values[:wallet_dir] = wallet_dir
+	end
 end.parse!
 
 config = MPW::Config.new(options[:config])
-cli    = MPW::Cli.new(config)
+cli    = MPW::Cli.new(config, nil)
 
-if options.key?(:init)
-  cli.setup(values)
-  cli.load_config
-  cli.get_wallet
-  cli.setup_gpg_key(values[:gpg_key])
+if not options[:init].nil?
+	cli.setup(values)
+	cli.load_config
+	cli.get_wallet
+	cli.setup_gpg_key(options[:init])
+	cli.setup_wallet_config
 else
-  cli.load_config
-  if values.empty?
-    cli.list_config
-  else
-    cli.set_config(values)
-  end
+	cli.set_config(values)
 end
diff --git a/bin/mpw-copy b/bin/mpw-copy
index fc3b6e0..0a14ed7 100644
--- a/bin/mpw-copy
+++ b/bin/mpw-copy
@@ -1,22 +1,20 @@
-#
-# Copyright:: 2013, Adrien Waksberg
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
+#!/usr/bin/ruby
+# MPW is a software to crypt and manage your passwords
+# Copyright (C) 2016  Adrien Waksberg <mpw@yae.im>
+# 
+# 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 2
+# 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 require 'optparse'
 require 'mpw/config'
@@ -27,40 +25,45 @@ require 'mpw/cli'
 # --------------------------------------------------------- #
 
 options             = {}
+options[:sync]      = true
 options[:clipboard] = true
 values              = {}
 
 OptionParser.new do |opts|
-  opts.banner = "#{I18n.t('option.usage')}: mpw copy [options]"
+	opts.banner = "#{I18n.t('option.usage')}: mpw copy [options]"
 
-  opts.on('-c', '--config PATH', I18n.t('option.config')) do |config|
-    options[:config] = config
-  end
+	opts.on('-c', '--config PATH', I18n.t('option.config')) do |config|
+		options[:config] = config
+	end
 
-  opts.on('-d', '--disable-clipboard', I18n.t('option.clipboard')) do
-    options[:clipboard] = false
-  end
+	opts.on('-d', '--disable-clipboard', I18n.t('option.clipboard')) do
+		options[:clipboard] = false
+	end
 
-  opts.on('-g', '--group NAME', I18n.t('option.group')) do |group|
-    values[:group] = group
-  end
+	opts.on('-g', '--group NAME', I18n.t('option.group')) do |group|
+		values[:group] = group
+	end
 
-  opts.on('-h', '--help', I18n.t('option.help')) do
-    puts opts
-    exit 0
-  end
+	opts.on('-h', '--help', I18n.t('option.help')) do
+		puts opts
+		exit 0
+	end
 
-  opts.on('-p', '--pattern PATTERN', I18n.t('option.pattern')) do |pattern|
-    values[:pattern] = pattern
-  end
+	opts.on('-n', '--no-sync', I18n.t('option.no_sync')) do
+		options[:sync] = false
+	end
 
-  opts.on('-w', '--wallet NAME', I18n.t('option.wallet')) do |wallet|
-    options[:wallet] = wallet
-  end
+	opts.on('-p', '--pattern PATTERN', I18n.t('option.pattern')) do |pattern|
+		values[:pattern] = pattern
+	end
+
+	opts.on('-w', '--wallet NAME', I18n.t('option.wallet')) do |wallet|
+		options[:wallet] = wallet
+	end
 end.parse!
 
 config = MPW::Config.new(options[:config])
-cli    = MPW::Cli.new(config)
+cli    = MPW::Cli.new(config, options[:sync])
 
 cli.load_config
 cli.get_wallet(options[:wallet])
diff --git a/bin/mpw-delete b/bin/mpw-delete
index 48eb792..2d79e6a 100644
--- a/bin/mpw-delete
+++ b/bin/mpw-delete
@@ -1,22 +1,20 @@
-#
-# Copyright:: 2013, Adrien Waksberg
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
+#!/usr/bin/ruby
+# MPW is a software to crypt and manage your passwords
+# Copyright (C) 2016  Adrien Waksberg <mpw@yae.im>
+# 
+# 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 2
+# 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 require 'optparse'
 require 'mpw/config'
@@ -26,36 +24,41 @@ require 'mpw/cli'
 # Options
 # --------------------------------------------------------- #
 
-options = {}
-values  = {}
+options        = {}
+options[:sync] = true
+values         = {}
 
 OptionParser.new do |opts|
-  opts.banner = "#{I18n.t('option.usage')}: mpw delete [options]"
+	opts.banner = "#{I18n.t('option.usage')}: mpw delete [options]"
 
-  opts.on('-c', '--config PATH', I18n.t('option.config')) do |config|
-    options[:config] = config
-  end
+	opts.on('-c', '--config PATH', I18n.t('option.config')) do |config|
+		options[:config] = config
+	end
 
-  opts.on('-g', '--group NAME', I18n.t('option.group')) do |group|
-    values[:group] = group
-  end
+	opts.on('-g', '--group NAME', I18n.t('option.group')) do |group|
+		values[:group] = group
+	end
 
-  opts.on('-h', '--help', I18n.t('option.help')) do
-    puts opts
-    exit 0
-  end
+	opts.on('-h', '--help', I18n.t('option.help')) do
+		puts opts
+		exit 0
+	end
 
-  opts.on('-p', '--pattern PATTERN', I18n.t('option.pattern')) do |pattern|
-    values[:pattern] = pattern
-  end
+	opts.on('-n', '--no-sync', I18n.t('option.no_sync')) do
+		options[:sync] = false
+	end
 
-  opts.on('-w', '--wallet NAME', I18n.t('option.wallet')) do |wallet|
-    options[:wallet] = wallet
-  end
+	opts.on('-p', '--pattern PATTERN', I18n.t('option.pattern')) do |pattern|
+		values[:pattern] = pattern
+	end
+
+	opts.on('-w', '--wallet NAME', I18n.t('option.wallet')) do |wallet|
+		options[:wallet] = wallet
+	end
 end.parse!
 
 config = MPW::Config.new(options[:config])
-cli    = MPW::Cli.new(config)
+cli    = MPW::Cli.new(config, options[:sync])
 
 cli.load_config
 cli.get_wallet(options[:wallet])
diff --git a/bin/mpw-export b/bin/mpw-export
index 92eb7bd..f891873 100644
--- a/bin/mpw-export
+++ b/bin/mpw-export
@@ -1,19 +1,20 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
+#!/usr/bin/ruby
+# MPW is a software to crypt and manage your passwords
+# Copyright (C) 2016  Adrien Waksberg <mpw@yae.im>
+# 
+# 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 2
+# 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 require 'optparse'
 require 'mpw/config'
@@ -23,40 +24,45 @@ require 'mpw/cli'
 # Options
 # --------------------------------------------------------- #
 
-options = {}
-values  = {}
+options        = {}
+options[:sync] = true
+values         = {}
 
 OptionParser.new do |opts|
-  opts.banner = "#{I18n.t('option.usage')}: mpw wallet [options]"
+	opts.banner = "#{I18n.t('option.usage')}: mpw wallet [options]"
 
-  opts.on('-c', '--config PATH', I18n.t('option.config')) do |config|
-    options[:config] = config
-  end
+	opts.on('-c', '--config PATH', I18n.t('option.config')) do |config|
+		options[:config] = config
+	end
 
-  opts.on('-f', '--file PATH', I18n.t('option.file_export')) do |file|
-    options[:file] = file
-  end
+	opts.on('-f', '--file PATH', I18n.t('option.file_export')) do |file|
+		options[:file] = file
+	end
 
-  opts.on('-g', '--group GROUP', I18n.t('option.group')) do |group|
-    values[:group] = group
-  end
+	opts.on('-g', '--group GROUP', I18n.t('option.group')) do |group|
+		values[:group] = group
+	end
 
-  opts.on('-h', '--help', I18n.t('option.help')) do
-    puts opts
-    exit 0
-  end
+	opts.on('-h', '--help', I18n.t('option.help')) do
+		puts opts
+		exit 0
+	end
 
-  opts.on('-p', '--pattern PATTERN', I18n.t('option.pattern')) do |pattern|
-    values[:pattern] = pattern
-  end
+	opts.on('-n', '--no-sync', I18n.t('option.no_sync')) do
+		options[:sync] = false
+	end
 
-  opts.on('-w', '--wallet NAME', I18n.t('option.wallet')) do |wallet|
-    options[:wallet] = wallet
-  end
+	opts.on('-p', '--pattern PATTERN', I18n.t('option.pattern')) do |pattern|
+		values[:pattern] = pattern
+	end
+
+	opts.on('-w', '--wallet NAME', I18n.t('option.wallet')) do |wallet|
+		options[:wallet] = wallet
+	end
 end.parse!
 
 config = MPW::Config.new(options[:config])
-cli    = MPW::Cli.new(config)
+cli    = MPW::Cli.new(config, options[:sync])
 
 cli.load_config
 cli.get_wallet(options[:wallet])
diff --git a/bin/mpw-genpwd b/bin/mpw-genpwd
index f6ca795..8af4290 100644
--- a/bin/mpw-genpwd
+++ b/bin/mpw-genpwd
@@ -1,19 +1,20 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
+#!/usr/bin/ruby
+# MPW is a software to crypt and manage your passwords
+# Copyright (C) 2016  Adrien Waksberg <mpw@yae.im>
+# 
+# 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 2
+# 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 require 'optparse'
 require 'mpw/mpw'
@@ -21,29 +22,29 @@ require 'mpw/mpw'
 options = {}
 
 OptionParser.new do |opts|
-  opts.banner = "#{I18n.t('option.usage')}: mpw passwd [options]"
+	opts.banner = "#{I18n.t('option.usage')}: mpw passwd [options]"
 
-  opts.on('-h', '--help', I18n.t('option.help')) do
-    puts opts
-    exit 0
-  end
+	opts.on('-h', '--help', I18n.t('option.help')) do
+		puts opts
+		exit 0
+	end
 
-  opts.on('-l', '--length NUMBER', I18n.t('option.length')) do |length|
-    options[:length] = length.to_i
-  end
+	opts.on('-l', '--length NUMBER', I18n.t('option.length')) do |length|
+		options[:length] = length.to_i
+	end
 
-  opts.on('-n', '--numeric', I18n.t('option.numeric')) do
-    options[:numeric] = true
-  end
+	opts.on('-n', '--numeric', I18n.t('option.numeric')) do
+		options[:numeric] = true
+	end
 
-  opts.on('-s', '--special-chars', I18n.t('option.special_chars')) do
-    options[:special] = true
-  end
+	opts.on('-s', '--special-chars', I18n.t('option.special_chars')) do
+		options[:special] = true
+	end
 
-  opts.on('-a', '--alpha', I18n.t('option.alpha')) do
-    options[:alpha] = true
-  end
+	opts.on('-a', '--alpha', I18n.t('option.alpha')) do
+		options[:alpha] = true
+	end
 end.parse!
 
-puts MPW::MPW.password(options)
+puts MPW::MPW::password(options)
 exit 0
diff --git a/bin/mpw-import b/bin/mpw-import
index d0deae9..c740252 100644
--- a/bin/mpw-import
+++ b/bin/mpw-import
@@ -1,19 +1,20 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
+#!/usr/bin/ruby
+# MPW is a software to crypt and manage your passwords
+# Copyright (C) 2016  Adrien Waksberg <mpw@yae.im>
+# 
+# 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 2
+# 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 require 'optparse'
 require 'mpw/config'
@@ -23,43 +24,38 @@ require 'mpw/cli'
 # Options
 # --------------------------------------------------------- #
 
-formats =
-  Dir["#{File.expand_path('../../lib/mpw/import', __FILE__)}/*.rb"]
-    .map { |v| File.basename(v, '.rb') }
-    .join(', ')
-options = {
-  format: 'mpw'
-}
+options        = {}
+options[:sync] = true
 
 OptionParser.new do |opts|
-  opts.banner = "#{I18n.t('option.usage')}: mpw import [options]"
+	opts.banner = "#{I18n.t('option.usage')}: mpw import [options]"
 
-  opts.on('-c', '--config PATH', I18n.t('option.config')) do |config|
-    options[:config] = config
-  end
+	opts.on('-c', '--config PATH', I18n.t('option.config')) do |config|
+		options[:config] = config
+	end
 
-  opts.on('-f', '--file PATH', I18n.t('option.file_import')) do |file|
-    options[:file] = file
-  end
+	opts.on('-f', '--file PATH', I18n.t('option.file_import')) do |file|
+		options[:file] = file
+	end
 
-  opts.on('-F', '--format STRING', I18n.t('option.file_format', formats: formats)) do |format|
-    options[:format] = format
-  end
+	opts.on('-h', '--help', I18n.t('option.help')) do
+		puts opts
+		exit 0
+	end
 
-  opts.on('-h', '--help', I18n.t('option.help')) do
-    puts opts
-    exit 0
-  end
+	opts.on('-n', '--no-sync', I18n.t('option.no_sync')) do
+		options[:sync] = false
+	end
 
-  opts.on('-w', '--wallet NAME', I18n.t('option.wallet')) do |wallet|
-    options[:wallet] = wallet
-  end
+	opts.on('-w', '--wallet NAME', I18n.t('option.wallet')) do |wallet|
+		options[:wallet] = wallet
+	end
 end.parse!
 
 config = MPW::Config.new(options[:config])
-cli    = MPW::Cli.new(config)
+cli    = MPW::Cli.new(config, options[:sync])
 
 cli.load_config
 cli.get_wallet(options[:wallet])
 cli.decrypt
-cli.import(options[:file], options[:format])
+cli.import(options[:file])
diff --git a/bin/mpw-list b/bin/mpw-list
index fb7899c..9d18b0d 100644
--- a/bin/mpw-list
+++ b/bin/mpw-list
@@ -1,19 +1,20 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
+#!/usr/bin/ruby
+# MPW is a software to crypt and manage your passwords
+# Copyright (C) 2016  Adrien Waksberg <mpw@yae.im>
+# 
+# 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 2
+# 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 require 'optparse'
 require 'mpw/config'
@@ -23,36 +24,41 @@ require 'mpw/cli'
 # Options
 # --------------------------------------------------------- #
 
-options = {}
-values  = {}
+options        = {}
+options[:sync] = true
+values         = {}
 
 OptionParser.new do |opts|
-  opts.banner = "#{I18n.t('option.usage')}: mpw list [options]"
+	opts.banner = "#{I18n.t('option.usage')}: mpw list [options]"
 
-  opts.on('-c', '--config PATH', I18n.t('option.config')) do |config|
-    options[:config] = config
-  end
+	opts.on('-c', '--config PATH', I18n.t('option.config')) do |config|
+		options[:config] = config
+	end
 
-  opts.on('-g', '--group NAME', I18n.t('option.group')) do |group|
-    values[:group] = group
-  end
+	opts.on('-g', '--group NAME', I18n.t('option.group')) do |group|
+		values[:group] = group
+	end
 
-  opts.on('-h', '--help', I18n.t('option.help')) do
-    puts opts
-    exit 0
-  end
+	opts.on('-h', '--help', I18n.t('option.help')) do
+		puts opts
+		exit 0
+	end
 
-  opts.on('-p', '--pattern PATTERN', I18n.t('option.pattern')) do |pattern|
-    values[:pattern] = pattern
-  end
+	opts.on('-p', '--pattern PATTERN', I18n.t('option.pattern')) do |pattern|
+		values[:pattern] = pattern
+	end
 
-  opts.on('-w', '--wallet NAME', I18n.t('option.wallet')) do |wallet|
-    options[:wallet] = wallet
-  end
+	opts.on('-n', '--no-sync', I18n.t('option.no_sync')) do
+		options[:sync] = false
+	end
+
+	opts.on('-w', '--wallet NAME', I18n.t('option.wallet')) do |wallet|
+		options[:wallet] = wallet
+	end
 end.parse!
 
 config = MPW::Config.new(options[:config])
-cli    = MPW::Cli.new(config)
+cli    = MPW::Cli.new(config, options[:sync])
 
 cli.load_config
 cli.get_wallet(options[:wallet])
diff --git a/bin/mpw-update b/bin/mpw-update
index 26a55c9..301b876 100644
--- a/bin/mpw-update
+++ b/bin/mpw-update
@@ -1,19 +1,20 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
+#!/usr/bin/ruby
+# MPW is a software to crypt and manage your passwords
+# Copyright (C) 2016  Adrien Waksberg <mpw@yae.im>
+# 
+# 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 2
+# 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 require 'optparse'
 require 'mpw/config'
@@ -23,70 +24,43 @@ require 'mpw/cli'
 # Options
 # --------------------------------------------------------- #
 
-values                = {}
-search                = {}
-options               = {}
-options[:text_editor] = false
+options        = {}
+options[:sync] = true
+values         = {}
 
 OptionParser.new do |opts|
-  opts.banner = "#{I18n.t('option.usage')}: mpw update [options]"
+	opts.banner = "#{I18n.t('option.usage')}: mpw update [options]"
 
-  opts.on('-c', '--config PATH', I18n.t('option.config')) do |config|
-    options[:config] = config
-  end
+	opts.on('-c', '--config PATH', I18n.t('option.config')) do |config|
+		options[:config] = config
+	end
 
-  opts.on('-C', '--comment COMMENT', I18n.t('option.comment')) do |comment|
-    values[:comment] = comment
-  end
+	opts.on('-g', '--group NAME', I18n.t('option.group')) do |group|
+		values[:group] = group
+	end
 
-  opts.on('-g', '--group NAME', I18n.t('option.group')) do |group|
-    search[:group] = group
-  end
+	opts.on('-h', '--help', I18n.t('option.help')) do
+		puts opts
+		exit 0
+	end
 
-  opts.on('-G', '--new-group NAME', I18n.t('option.new_group')) do |group|
-    values[:group] = group
-  end
+	opts.on('-n', '--no-sync', I18n.t('option.no_sync')) do
+		options[:sync] = false
+	end
 
-  opts.on('-h', '--help', I18n.t('option.help')) do
-    puts opts
-    exit 0
-  end
+	opts.on('-p', '--pattern PATTERN', I18n.t('option.pattern')) do |pattern|
+		values[:pattern] = pattern
+	end
 
-  opts.on('-o', '--otp-code CODE', I18n.t('option.otp_code')) do |otp|
-    values[:otp_key] = otp
-  end
-
-  opts.on('-p', '--pattern PATTERN', I18n.t('option.pattern')) do |pattern|
-    search[:pattern] = pattern
-  end
-
-  opts.on('-r', '--random', I18n.t('option.random_password')) do
-    options[:password] = true
-  end
-
-  opts.on('-t', '--text-editor', I18n.t('option.text_editor')) do
-    options[:text_editor] = true
-  end
-
-  opts.on('-u', '--url URL', I18n.t('option.url')) do |url|
-    values[:url] = url
-  end
-
-  opts.on('-U', '--user USER', I18n.t('option.user')) do |user|
-    values[:user] = user
-  end
-
-  opts.on('-w', '--wallet NAME', I18n.t('option.wallet')) do |wallet|
-    options[:wallet] = wallet
-  end
+	opts.on('-w', '--wallet NAME', I18n.t('option.wallet')) do |wallet|
+		options[:wallet] = wallet
+	end
 end.parse!
 
 config = MPW::Config.new(options[:config])
-cli    = MPW::Cli.new(config)
-
-options[:text_editor] = true if values.empty?
+cli    = MPW::Cli.new(config, options[:sync])
 
 cli.load_config
 cli.get_wallet(options[:wallet])
 cli.decrypt
-cli.update(options[:password], options[:text_editor], search, values)
+cli.update(values)
diff --git a/bin/mpw-wallet b/bin/mpw-wallet
index 6518283..b81e6b2 100644
--- a/bin/mpw-wallet
+++ b/bin/mpw-wallet
@@ -1,19 +1,20 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
+#!/usr/bin/ruby
+# MPW is a software to crypt and manage your passwords
+# Copyright (C) 2016  Adrien Waksberg <mpw@yae.im>
+# 
+# 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 2
+# 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 require 'optparse'
 require 'mpw/config'
@@ -23,68 +24,68 @@ require 'mpw/cli'
 # Options
 # --------------------------------------------------------- #
 
-options          = {}
-options[:delete] = false
+options        = {}
+options[:sync] = {}
+values         = {}
 
 OptionParser.new do |opts|
-  opts.banner = "#{I18n.t('option.usage')}: mpw wallet [options]"
+	opts.banner = "#{I18n.t('option.usage')}: mpw wallet [options]"
 
-  opts.on('-a', '--add-gpg-key NAME', I18n.t('option.add_gpg_key')) do |gpg_key|
-    options[:gpg_key] = gpg_key
-  end
+	opts.on('-c', '--config PATH', I18n.t('option.config')) do |config|
+		options[:config] = config
+	end
 
-  opts.on('-c', '--config PATH', I18n.t('option.config')) do |config|
-    options[:config] = config
-  end
+	opts.on('-h', '--help', I18n.t('option.help')) do
+		puts opts
+		exit 0
+	end
 
-  opts.on('-d', '--delete-gpg-key NAME', I18n.t('option.delete_gpg_key')) do |gpg_key|
-    options[:gpg_key] = gpg_key
-    options[:delete]  = true
-  end
+	opts.on('--host NAME', I18n.t('option.host')) do |host|
+		values[:host] = host
+	end
 
-  opts.on('-h', '--help', I18n.t('option.help')) do
-    puts opts
-    exit 0
-  end
+	opts.on('-l', '--list', I18n.t('option.list')) do |list|
+		options[:list] = true
+	end
 
-  opts.on('-l', '--list', I18n.t('option.list')) do
-    options[:list] = true
-  end
+	opts.on('-n', '--no-sync', I18n.t('option.no_sync')) do
+		options[:sync] = false
+	end
 
-  opts.on('-L', '--list-keys', I18n.t('option.list_keys')) do
-    options[:list_keys] = true
-  end
+	opts.on('--password', I18n.t('option.password')) do
+		values[:password] = true
+	end
 
-  opts.on('-p', '--path PATH', I18n.t('option.path')) do |path|
-    options[:path] = path
-  end
+	opts.on('--path PATH', I18n.t('option.path')) do |path|
+		values[:path] = path
+	end
 
-  opts.on('-P', '--default-path', I18n.t('option.default_path')) do
-    options[:path] = 'default'
-  end
+	opts.on('--port NUMBER', I18n.t('option.port')) do |port|
+		values[:port] = port
+	end
 
-  opts.on('-w', '--wallet NAME', I18n.t('option.wallet')) do |wallet|
-    options[:wallet] = wallet
-  end
+	opts.on('--protocol NAME', I18n.t('option.protocol')) do |protocol|
+		values[:protocol] = protocol
+	end
+
+	opts.on('--user NAME', I18n.t('option.user')) do |user|
+		values[:user] = user
+	end
+
+	opts.on('-w', '--wallet NAME', I18n.t('option.wallet')) do |wallet|
+		options[:wallet] = wallet
+	end
 end.parse!
 
 config = MPW::Config.new(options[:config])
-cli    = MPW::Cli.new(config)
+cli    = MPW::Cli.new(config, options[:sync])
 
 cli.load_config
 
-if options.key?(:path)
-  cli.get_wallet(options[:wallet])
-  cli.set_wallet_path(options[:path])
-elsif options.key?(:list_keys) || options.key?(:gpg_key)
-  cli.get_wallet(options[:wallet])
-  cli.decrypt
-
-  if options.key?(:list_keys)
-    cli.list_keys
-  elsif options.key?(:gpg_key)
-    options[:delete] ? cli.delete_key(options[:gpg_key]) : cli.add_key(options[:gpg_key])
-  end
+if not options[:list].nil?
+	cli.list_wallet
 else
-  cli.list_wallet
+	cli.get_wallet(options[:wallet])
+	cli.decrypt
+	cli.setup_wallet_config(values)
 end
diff --git a/i18n/en.yml b/i18n/en.yml
index 9df7bda..93955d5 100644
--- a/i18n/en.yml
+++ b/i18n/en.yml
@@ -1,31 +1,37 @@
 ---
 en:
   error:
-    bad_class: "The object class isn't valid!"
     config:
       write: "Can't write the config file!"
       load: "Checkconfig failed!"
-      key_bad_format: "The key string isn't in the right format!"
-      no_key_public: "You haven't entered the public key of %{key}!"
+      key_bad_format: "The key string isn't in good format!"
+      no_key_public: "You haven't the public key of %{key}!"
       genkey_gpg:
         exception: "Can't create the GPG key!"
         name: "You must define a name for your GPG key!"
         password: "You must define a password for your GPG key!"
-    empty: "The class is empty"
     export: "Can't export, unable to write in %{file}!"
-    export_key: "Can't export the GPG key"
     gpg_file: 
       decrypt: "Can't decrypt file!"
       encrypt: "Can't encrypt the GPG file!"
     mpw_file: 
-      read_data: "Can't read the MPW file!"
-      write_data: "Can't write the MPW file!"
+      read_data: "Can't to read the MPW file!"
+      write_data: "Can't to write the MPW file!"
     import: "Can't import, unable to read %{file}!"
     update:
-      host_and_comment_empty: "You must define a host or a comment!"
+      name_empty: "You must define a name!"
+    sync: 
+      general: "An error has appeared during the sync"
+      connection: "Connection fail!"
+      communication: "A communication problem with the server is appeared!"
+      download: "Can't download the file!"
+      not_authorized: "You haven't the access to remote file!"
+      upload: "Can't upload the file on the server!"
+      unknown: "An unknown error is occured!"
+      unknown_type: "The sync type is unknown"
 
   warning:
-    select: 'Your choice is not a valid item!'
+    select: 'Your choice is not a valid element!'
 
   command:
     add: "Add a new item"
@@ -41,68 +47,53 @@ en:
 
   option:
     add: "Add an item or key"
-    add_gpg_key: "Share the wallet with another GPG key"
-    alpha: "Use letter to create a password"
-    comment: "Specify a comment"
+    alpha: "Use letter to generate a password"
     config: "Specify the configuration file to use"
     clipboard: "Disable the clipboard feature"
-    default_path: "Move the wallet to the default directory"
-    default_wallet: "Specify the default wallet to use"
-    delete_gpg_key: "Delete wallet sharing with an other GPG key"
-    disable_alpha: "Don't use letters to create a password"
-    disable_numeric: "Don't use numbers to generate a password"
-    disable_pinmode: "Disable the pinentry mode"
-    disable_special_chars: "Don't use special char to create a password"
     export: "Export a wallet in an yaml file"
-    file_export: "Specify the file to export data"
-    file_format: "Format of import file (default: mpw; available: %{formats})"
+    file_export: "Specify the file where export data"
     file_import: "Specify the file to import"
-    force: "Do not ask confirmation when deleting an item"
-    generate_password: "Create a random password (default 8 characters)"
+    force: "No ask to confirm when you delete an item"
+    generate_password: "Generate a random password (default 8 characters)"
     gpg_exe: "Set the gpg binary path to use"
     gpg_key: "Specify a GPG key (ex: user@example.com)"
     group: "Search the items with specified group"
     help: "Show this help message"
+    host: "Specify the server for the synchronization"
     init: "Initialize mpw"
-    import: "Import item from an yaml file"
-    key: "Define the key name"
+    import: "Import item since a yaml file"
+    key: "Specify the key name"
     lang: "Set the software language"
-    length: "Size of the password"
     list: "List the wallets"
-    list_keys: "List the GPG keys in wallet"
-    new_group: "Define a group for the item"
-    numeric: "Use number to create a password"
-    otp_code: "Set an otp key"
-    path: "Move the wallet in new specify directory"
+    no_sync: "Disable synchronization with the server"
+    numeric: "Use number to generate a password"
+    password: "Change the password for the synchronization"
+    path: "Specify the remote path"
     pattern: "Given search pattern"
-    pinmode: "Enable pinentry mode (available with gpg >= 2.1)"
-    random_password: "Generate a random password"
+    port: "Specify the connection port"
+    protocol: "Specify the protocol for the connection"
     setup: "Create a new configuration file" 
     setup_wallet: "Create a new configuration file for a wallet"
-    special_chars: "Use special char to create a password"
-    show: "Search and display the items"
-    show_all: "Listing all items"
-    text_editor: "Use text editor to edit the item"
-    usage: "Use"
-    url: "Set an url (ex: https://example.com/path)"
-    user: "Set an user"
+    special_chars: "Use special char to generate a password"
+    show: "Search and show the items"
+    show_all: "List all items"
+    usage: "Usage"
+    user: "Specify the user for the connection"
     wallet: "Specify a wallet to use"
     wallet_dir: "Set the wallets folder"
 
   form:
-    select:
-      choice: "Select the item: "
-      error: "No item selected"
+    select: "Select the item: "
     add_key:
       valid: "Key has been added!"
     add_item:
-      name: "Item name (mandatory)"
-      group: "Group name"
-      host: "Hostname or ip"
-      protocol: "Connection protocol (ssh, http, ...)"
-      login: "Connection ID"
-      password: "Password"
-      port: "Connection port"
+      name: "The item's name (mandatory"
+      group: "The group's name"
+      host: "The hostname or ip"
+      protocol: "The protocol of the connection (ssh, http, ...)"
+      login: "The login of connection"
+      password: "The password"
+      port: "The connection port"
       comment: "A comment"
       otp_key: "The OTP secret"
       valid: "Item has been added!"
@@ -111,11 +102,9 @@ en:
       clean: "The clipboard has been cleaned."
       login: "The login has been copied in clipboard."
       password: "The password has been copied in clipboard for 30s!"
-      otp: "The OTP code has been copied %{time}s!"
-      url: "The URL has been copied in clipboard."
+      otp: "The OTP code has been copied for %{time}s!"
       help:
          name: "Help"
-         url: "Press <u> to copy URL"
          login: "Press <l> to copy the login"
          password: "Press <p> to copy the password"
          otp_code: "Press <o> to copy the otp code"
@@ -123,19 +112,12 @@ en:
     delete_key:
       valid: "Key has been deleted!"
     delete_item:
-      ask: "Are you sure you want to remove this item ?"
+      ask: "Are you sure you want to remove the item ?"
       valid: "The item has been removed!"
     import:
       ask: "Are you sure you want to import this file %{file} ?"
-      file_empty: "The import file is empty!"
-      file_not_exist: "The import file doesn't exist!"
-      format_unknown: "The import format '%{file_format} is unknown!"
-      valid: "The import is successful!"
+      valid: "The import is succesfull!"
       not_valid: "No data to import!"
-    set_config:
-      valid: "The config file has been edited!"
-    set_wallet_path:
-      valid: "The wallet has been moved!"
     setup_config:
       title: "Setup a new config file"
       lang: "Choose your language (en, fr, ...) [default=%{lang}]: "
@@ -143,38 +125,45 @@ en:
       gpg_exe: "Enter the executable GPG path (optional): "
       wallet_dir: "Enter the wallets's folder path [default=%{home}/wallets]: "
       valid: "The config file has been created!"
+    setup_wallet:
+      title: "Wallet setup"
+      sync_type: "Synchronization type (ssh, ftp): "
+      sync_host: "Synchronization server: "
+      sync_port: "Port of the synchronization server: "
+      sync_user: "Username for the synchronization: "
+      sync_pwd: "Password for the synchronization: "
+      sync_path: "File path for the synchronization : "
+      valid: "The wallet config file has been created!"
     setup_gpg_key:
       title: "Setup a GPG key"
-      ask: "Do you want to create your GPG key ? (Y/n)"
-      no_create: "You must to create manually your GPG key or relaunch the software."
+      ask: "Do you want create your GPG key ? (Y/n)"
+      no_create: "You must create manually your GPG key or relaunch the software."
       name: "Your name and lastname: "
       password: "A password for the GPG key: "
       confirm_password: "Confirm your password: "
       error_password: "Your passwords aren't identical!" 
       length: "Size of the GPG key [default=2048]: "
       expire: "Expire time of the GPG key [default=0 (unlimited)]: "
-      wait: "Please wait until GPG key is created, this process can take a few minutes."
+      wait: "Please waiting during the GPG key generate, this process can take few minutes."
       valid: "Your GPG key has been created ;-)"
     update_item:
-      name: "Item name (mandatory)"
-      group: "Group name"
-      host: "Hostname or ip"
-      protocol: "Connection protocol (ssh, http, ...)"
-      login: "Login id"
-      password: "Password (leave empty if you don't want to update it)"
-      port: "Connection port"
+      name: "The item's name (mandatory"
+      group: "The group's name"
+      host: "The hostname or ip"
+      protocol: "The protocol of the connection (ssh, http, ...)"
+      login: "The login of connection"
+      password: "The password (leave empty if you don't want change)"
+      port: "The connection port"
       comment: "A comment"
-      otp_key: "Secret OTP (leave empty if you don't want to update it"
+      otp_key: "The OTP secret (leave empty if you don't want change"
       valid: "Item has been updated!" 
     export:
-      valid: "The export in %{file} is successful!"
+      valid: "The export in %{file} is succesfull!"
 
   display:
     comment: "Comment"
-    config: "Configuration"
     error: "ERROR"
-    keys: "GPG keys"
-    gpg_password: "GPG password: "
+    gpg_password: "GPG passphrase: "
     group: "Group"
     login: "Login"
     name: "Name"
@@ -185,7 +174,6 @@ en:
     port: "Port"
     protocol: "Protocol"
     server: "Server"
-    wallets: "Wallets"
     warning: "Warning"
 
   formats:
diff --git a/i18n/fr.yml b/i18n/fr.yml
index 04583ad..4f8b7ac 100644
--- a/i18n/fr.yml
+++ b/i18n/fr.yml
@@ -1,7 +1,6 @@
 ---
 fr:
   error:
-    bad_class: "La classe de l'objet n'est pas celle attendue!"
     config:
       write: "Impossible d'écrire le fichier de configuration!"
       load: "Le fichier de configuration est invalide!"
@@ -11,9 +10,7 @@ fr:
         exception: "La création de la clé GPG n'a pas pu aboutir!"
         name: "Vous devez définir un nom pour votre clé GPG!"
         password: "Vous devez définir un mot de passe pour votre clé GPG!"
-    empty: "La classe est vide"
     export: "Impossible d'exporter les données dans le fichier %{file}!"
-    export_key: "Impossible d'exporter la clé GPG"
     gpg_file: 
       decrypt: "Impossible de déchiffrer le fichier GPG!"
       encrypt: "Impossible de chiffrer le fichier GPG!"
@@ -22,7 +19,16 @@ fr:
       write_data: "Impossible d'écrire le fichier MPW!"
     import: "Impossible d'importer le fichier %{file}, car il n'est pas lisible!"
     update:
-      host_and_comment_empty: "Vous devez définir un host ou un commentaire!"
+      name_empty: "Vous devez définir un nom!"
+    sync: 
+      general: "Une erreur est survenue durant la synchronisation"
+      connection: "La connexion n'a pu être établie!"
+      communication: "Un problème de communication avec le serveur est apparu!"
+      download: "Impossible de télécharger le fichier!"
+      not_authorized: "Vous n'avez pas les autorisations d'accès au fichier distant!"
+      upload: "Impossible d'envoyer le fichier sur le serveur!"
+      unknown: "Une erreur inconnue est survenue!"
+      unknown_type: "Le type de synchronisation est inconnu"
 
   warning:
     select: "Votre choix n'est pas un élément valide!"
@@ -41,21 +47,11 @@ fr:
 
   option:
     add: "Ajoute un élément ou une clé"
-    add_gpg_key: "Partage le portefeuille avec une autre clé GPG"
     alpha: "Utilise des lettres dans la génération d'un mot de passe"
     config: "Spécifie le fichier de configuration à utiliser"
-    comment: "Spécifie un commentaire"
     clipboard: "Désactive la fonction presse papier"
-    default_path: "Déplace le portefeuille dans le dossier par défaut"
-    default_wallet: "Spécifie le porte-feuille à utiliser par défaut"
-    delete_gpg_key: "Supprime le partage le portefeuille avec une autre clé GPG"
-    disable_alpha: "Désactive l'utilisation des lettres dans la génération d'un mot de passe"
-    disable_numeric: "Désactive l'utilisation des chiffre dans la génération d'un mot de passe"
-    disable_pinmode: "Désactive le mode pinentry"
-    disable_special_chars: "Désactive l'utilisation des charactères speciaux dans la génération d'un mot de passe"
     export: "Exporte un portefeuille dans un fichier yaml"
     file_export: "Spécifie le fichier où exporter les données"
-    file_format: "Format du fichier d'import (défault: mpw; disponible: %{formats})"
     file_import: "Spécifie le fichier à importer"
     force: "Ne demande pas de confirmation pour la suppression d'un élément"
     generate_password: "Génére un mot de passe aléatoire (défaut 8 caractères)"
@@ -63,36 +59,31 @@ fr:
     gpg_key: "Spécifie une clé GPG (ex: user@example.com)"
     group: "Recherche les éléments appartenant au groupe spécifié"
     help: "Affiche ce message d'aide"
+    host: "Spécifie le serveur pour la synchronisation"
     import: "Importe des éléments depuis un fichier yaml"
     init: "Initialise mpw"
     key: "Spécifie le nom d'une clé"
     lang: "Spécifie la langue du logiciel (ex: fr)"
-    length: "Taille du mot de passe"
     list: "Liste les portefeuilles"
-    list_keys: "Liste les clés GPG dans le portefeuille"
-    new_group: "Spécifie le groupe de l'item"
+    no_sync: "Désactive la synchronisation avec le serveur"
     numeric: "Utilise des chiffre dans la génération d'un mot de passe"
-    otp_code: "Spécifie un code OTP"
-    path: "Déplace le portefeuille dans un nouveau dossier"
+    password: "Changer le mot de passe de connexion"
+    path: "Spécifie le chemin distant"
     pattern: "Motif de donnée à chercher"
-    pinmode: "Active le mode pinentry (valable avec gpg >= 2.1)"
-    random_password: "Génére un mot de passe aléatoire"
+    port: "Spécifie le port de connexion"
+    protocol: "Spécifie le protocol utilisé pour la connexion"
     setup: "Création d'un nouveau fichier de configuration"
     setup_wallet: "Création d'un nouveau fichier de configuration pour un portefeuille"
     special_chars: "Utilise des charactères speciaux dans la génération d'un mot de passe"
     show: "Recherche et affiche les éléments"
     show_all: "Liste tous les éléments"
-    text_editor: "Active l'édition avec un éditeur de texte"
     usage: "Utilisation"
-    url: "Spécifie l'url (ex: http://example.com/path)"
-    user: "Spécifie un utilisateur"
+    user: "Spécifie l'identifiant de connection"
     wallet: "Spécifie le portefeuille à utiliser"
     wallet_dir: "Spécifie le répertoire des portefeuilles"
 
   form:
-    select:
-      choice: "Sélectionner l'élément: "
-      error: "Aucun élément sélectionné"
+    select: "Sélectionner l'élément: "
     add_key:
       valid: "La clé a bien été ajoutée!"
     add_item:
@@ -112,10 +103,8 @@ fr:
       login: "L'identifiant a été copié dans le presse papier"
       password: "Le mot de passe a été copié dans le presse papier pour 30s!"
       otp: "Le code OTP a été copié dans le presse papier il est valable %{time}s!"
-      url: "L'URL a été copié dans le presse papier"
       help:
          name: "Aide"
-         url: "Pressez <u> pour copier l'URL"
          login: "Pressez <l> pour copier l'identifiant"
          password: "Pressez <p> pour copier le mot de passe"
          otp_code: "Pressez <o> pour copier le code OTP"
@@ -127,15 +116,8 @@ fr:
       valid: "L'élément a bien été supprimé!"
     import:
       ask: "Êtes vous sûre de vouloir importer le fichier %{file} ?"
-      file_empty: "Le fichier d'import est vide!"
-      file_not_exist: "Le fichier d'import n'existe pas"
-      format_unknown: "Le format d'import '%{file_format}' est inconnu!"
       valid: "L'import est un succès!"
       not_valid: "Aucune donnée à importer!"
-    set_config:
-      valid: "Le fichier de configuration a bien été modifié!"
-    set_wallet_path:
-      valid: "Le portefeuille a bien été déplacé!"
     setup_config:
       title: "Création d'un nouveau fichier de configuration"
       lang: "Choisissez votre langue (en, fr, ...) [défaut=%{lang}]: "
@@ -143,6 +125,15 @@ fr:
       gpg_exe: "Entrez le chemin de l'exécutable GPG (optionnel): "
       wallet_dir: "Entrez le chemin du répertoire qui contiendra les porte-feuilles de mot de passe [défaut=%{home}/wallets]: "
       valid: "Le fichier de configuration a bien été créé!"
+    setup_wallet:
+      title: "Configuration du porte-feuille"
+      sync_type: "Type de synchronisation (ssh, ftp): "
+      sync_host: "Serveur: "
+      sync_port: "Port: "
+      sync_user: "Utilisateur: "
+      sync_pwd: "Mot de passe: "
+      sync_path: "Chemin du fichier: "
+      valid: "Le fichier de configuration du porte-feuille a bien été créé!"
     setup_gpg_key:
       title: "Configuration d'une nouvelle clé GPG"
       ask: "Voulez vous créer votre clé GPG ? (O/n)"
@@ -171,9 +162,7 @@ fr:
 
   display:
     comment: "Commentaire"
-    config: "Configuration"
     error: "ERREUR"
-    keys: "Clés GPG"
     gpg_password: "Mot de passe GPG: "
     group: "Groupe"
     login: "Identifiant"
@@ -185,7 +174,6 @@ fr:
     port: "Port"
     protocol: "Protocol"
     server: "Serveur"
-    wallets: "Porte-feuilles"
     warning: "Warning"
 
   formats:
diff --git a/lib/mpw/cli.rb b/lib/mpw/cli.rb
index e64d27c..722b16d 100644
--- a/lib/mpw/cli.rb
+++ b/lib/mpw/cli.rb
@@ -1,22 +1,20 @@
-#
-# Copyright:: 2013, Adrien Waksberg
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
+#!/usr/bin/ruby
+# MPW is a software to crypt and manage your passwords
+# Copyright (C) 2016  Adrien Waksberg <mpw@yae.im>
+# 
+# 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 2
+# 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 require 'readline'
 require 'locale'
@@ -29,595 +27,521 @@ require 'mpw/item'
 require 'mpw/mpw'
 
 module MPW
-  class Cli
-    # @param config [Config]
-    def initialize(config)
-      @config = config
-    end
-
-    # Change a parameter int the config after init
-    # @param options [Hash] param to change
-    def set_config(options)
-      @config.setup(options)
-
-      puts I18n.t('form.set_config.valid').to_s.green
-    rescue => e
-      puts "#{I18n.t('display.error')} #15: #{e}".red
-      exit 2
-    end
-
-    # Change the wallet path
-    # @param path [String] new path
-    def set_wallet_path(path)
-      @config.set_wallet_path(path, @wallet)
-
-      puts I18n.t('form.set_wallet_path.valid').to_s.green
-    rescue => e
-      puts "#{I18n.t('display.error')} #19: #{e}".red
-      exit 2
-    end
-
-    # Create a new config file
-    # @param options [Hash]
-    def setup(options)
-      options[:lang] = options[:lang] || Locale::Tag.parse(ENV['LANG']).to_simple.to_s[0..1]
-
-      I18n.locale = options[:lang].to_sym
-
-      @config.setup(options)
-
-      load_config
-
-      puts I18n.t('form.setup_config.valid').to_s.green
-    rescue => e
-      puts "#{I18n.t('display.error')} #8: #{e}".red
-      exit 2
-    end
-
-    # Setup a new GPG key
-    # @param gpg_key [String] gpg key name
-    def setup_gpg_key(gpg_key)
-      return if @config.check_gpg_key?
-
-      password = ask(I18n.t('form.setup_gpg_key.password')) { |q| q.echo = false }
-      confirm  = ask(I18n.t('form.setup_gpg_key.confirm_password')) { |q| q.echo = false }
-
-      raise I18n.t('form.setup_gpg_key.error_password') if password != confirm
-
-      @password = password.to_s
-
-      puts I18n.t('form.setup_gpg_key.wait')
-
-      @config.setup_gpg_key(@password, gpg_key)
-
-      puts I18n.t('form.setup_gpg_key.valid').to_s.green
-    rescue => e
-      puts "#{I18n.t('display.error')} #8: #{e}".red
-      exit 2
-    end
-
-    # List gpg keys in wallet
-    def list_keys
-      table_list('keys', @mpw.list_keys)
-    end
-
-    # List config
-    def list_config
-      config = {
-        'lang'           => @config.lang,
-        'gpg_key'        => @config.gpg_key,
-        'default_wallet' => @config.default_wallet,
-        'wallet_dir'     => @config.wallet_dir,
-        'pinmode'        => @config.pinmode,
-        'gpg_exe'        => @config.gpg_exe
-      }
-
-      @config.wallet_paths.each { |k, v| config["path_wallet_#{k}"] = "#{v}/#{k}.mpw" }
-      @config.password.each     { |k, v| config["password_#{k}"] = v }
-
-      table_list('config', config)
-    end
-
-    # Load config
-    def load_config
-      @config.load_config
-    rescue => e
-      puts "#{I18n.t('display.error')} #10: #{e}".red
-      exit 2
-    end
-
-    # Request the GPG password and decrypt the file
-    def decrypt
-      if defined?(@mpw)
-        @mpw.read_data
-      else
-        begin
-          @mpw = MPW.new(@config.gpg_key, @wallet_file, nil, @config.gpg_exe, @config.pinmode)
-
-          @mpw.read_data
-        rescue
-          @password = ask(I18n.t('display.gpg_password')) { |q| q.echo = false }
-          @mpw      = MPW.new(@config.gpg_key, @wallet_file, @password, @config.gpg_exe, @config.pinmode)
-
-          @mpw.read_data
-        end
-      end
-    rescue => e
-      puts "#{I18n.t('display.error')} #11: #{e}".red
-      exit 2
-    end
-
-    # Format list on a table
-    # @param title [String] name of table
-    # @param list  an array or hash
-    def table_list(title, list)
-      length = { k: 0, v: 0 }
-
-      if list.is_a?(Array)
-        i    = 0
-        list = list.map do |item|
-          i += 1
-          [i, item]
-        end.to_h
-      end
-
-      list.each do |k, v|
-        length[:k] = k.to_s.length if length[:k] < k.to_s.length
-        length[:v] = v.to_s.length if length[:v] < v.to_s.length
-      end
-
-      puts "\n#{I18n.t("display.#{title}")}".red
-      print ' '
-      (length[:k] + length[:v] + 5).times { print '=' }
-      print "\n"
-
-      list.each do |k, v|
-        print "  #{k}".cyan
-        (length[:k] - k.to_s.length + 1).times { print ' ' }
-        puts "| #{v}"
-      end
-
-      print "\n"
-    end
-
-    # Format items on a table
-    # @param items [Array]
-    def table_items(items = [])
-      group        = '.'
-      i            = 1
-      length_total = 10
-      data         = { id:       { length: 3,  color: 'cyan' },
-                       host:     { length: 9,  color: 'yellow' },
-                       user:     { length: 7,  color: 'green' },
-                       otp:      { length: 4,  color: 'white' },
-                       comment:  { length: 14, color: 'magenta' } }
-
-      items.each do |item|
-        data.each do |k, v|
-          case k
-          when :id, :otp
-            next
-          when :host
-            v[:length] = item.url.length + 3 if item.url.length >= v[:length]
-          else
-            v[:length] = item.send(k.to_s).to_s.length + 3 if item.send(k.to_s).to_s.length >= v[:length]
-          end
-        end
-      end
-      data[:id][:length] = items.length.to_s.length + 2 if items.length.to_s.length > data[:id][:length]
-
-      data.each_value { |v| length_total += v[:length] }
-      items.sort!     { |a, b| a.group.to_s.downcase <=> b.group.to_s.downcase }
-
-      items.each do |item|
-        if group != item.group
-          group = item.group
-
-          if group.to_s.empty?
-            puts "\n#{I18n.t('display.no_group')}".red
-          else
-            puts "\n#{group}".red
-          end
-
-          print ' '
-          length_total.times { print '=' }
-          print "\n "
-          data.each do |k, v|
-            case k
-            when :id
-              print ' ID'
-            when :otp
-              print '| OTP'
-            else
-              print "| #{k.to_s.capitalize}"
-            end
-
-            (v[:length] - k.to_s.length).times { print ' ' }
-          end
-          print "\n "
-          length_total.times { print '=' }
-          print "\n"
-        end
-
-        print "  #{i}".send(data[:id][:color])
-        (data[:id][:length] - i.to_s.length).times { print ' ' }
-        data.each do |k, v|
-          next if k == :id
-
-          print '| '
-
-          case k
-          when :otp
-            item.otp ? (print ' X  ') : 4.times { print ' ' }
-
-          when :host
-            print "#{item.protocol}://".light_black if item.protocol
-            print item.host.send(v[:color])
-            print ":#{item.port}".light_black if item.port
-            (v[:length] - item.url.to_s.length).times { print ' ' }
-
-          else
-            print item.send(k.to_s).to_s.send(v[:color])
-            (v[:length] - item.send(k.to_s).to_s.length).times { print ' ' }
-          end
-        end
-        print "\n"
-
-        i += 1
-      end
-
-      print "\n"
-    end
-
-    # Display the query's result
-    # @param options [Hash] the options to search
-    def list(**options)
-      result = @mpw.list(options)
-
-      if result.empty?
-        puts I18n.t('display.nothing')
-      else
-        table_items(result)
-      end
-    end
-
-    # Get an item when multiple choice
-    # @param items [Array] list of items
-    # @return [Item] an item
-    def get_item(items)
-      return items[0] if items.length == 1
-
-      items.sort! { |a, b| a.group.to_s.downcase <=> b.group.to_s.downcase }
-      choice = ask(I18n.t('form.select.choice')).to_i
-
-      raise I18n.t('form.select.error') unless choice >= 1 && choice <= items.length
-
-      items[choice - 1]
-    end
-
-    # Print help message for clipboard mode
-    # @param item [Item]
-    def clipboard_help(item)
-      puts "----- #{I18n.t('form.clipboard.help.name')} -----".cyan
-      puts I18n.t('form.clipboard.help.url')
-      puts I18n.t('form.clipboard.help.login')
-      puts I18n.t('form.clipboard.help.password')
-      puts I18n.t('form.clipboard.help.otp_code') if item.otp
-      puts I18n.t('form.clipboard.help.quit')
-    end
-
-    # Copy in clipboard the login and password
-    # @param item [Item]
-    # @param clipboard [Boolean] enable clipboard
-    def clipboard(item, clipboard = true)
-      # Security: force quit after 90s
-      Thread.new do
-        sleep 90
-        exit
-      end
-
-      Kernel.loop do
-        choice = ask(I18n.t('form.clipboard.choice')).to_s
-
-        case choice
-        when 'q', 'quit'
-          break
-
-        when 'u', 'url'
-          if clipboard
-            Clipboard.copy(item.url)
-            puts I18n.t('form.clipboard.url').green
-          else
-            puts item.url
-          end
-
-        when 'l', 'login'
-          if clipboard
-            Clipboard.copy(item.user)
-            puts I18n.t('form.clipboard.login').green
-          else
-            puts item.user
-          end
-
-        when 'p', 'password'
-          if clipboard
-            Clipboard.copy(@mpw.get_password(item.id))
-            puts I18n.t('form.clipboard.password').yellow
-
-            Thread.new do
-              sleep 30
-
-              Clipboard.clear
-            end
-          else
-            puts @mpw.get_password(item.id)
-          end
-
-        when 'o', 'otp'
-          if !item.otp
-            clipboard_help(item)
-            next
-          elsif clipboard
-            Clipboard.copy(@mpw.get_otp_code(item.id))
-          else
-            puts @mpw.get_otp_code(item.id)
-          end
-          puts I18n.t('form.clipboard.otp', time: @mpw.get_otp_remaining_time).yellow
-
-        else
-          clipboard_help(item)
-        end
-      end
-
-      Clipboard.clear
-    rescue SystemExit, Interrupt
-      Clipboard.clear
-    end
-
-    # List all wallets
-    def list_wallet
-      wallets = @config.wallet_paths.keys
-
-      Dir.glob("#{@config.wallet_dir}/*.mpw").each do |f|
-        wallet = File.basename(f, '.mpw')
-        wallet += ' *'.green if wallet == @config.default_wallet
-        wallets << wallet
-      end
-
-      table_list('wallets', wallets)
-    end
-
-    # Display the wallet
-    # @param wallet [String] wallet name
-    def get_wallet(wallet = nil)
-      @wallet =
-        if wallet.to_s.empty?
-          wallets = Dir.glob("#{@config.wallet_dir}/*.mpw")
-          if wallets.length == 1
-            File.basename(wallets[0], '.mpw')
-          elsif !@config.default_wallet.to_s.empty?
-            @config.default_wallet
-          else
-            'default'
-          end
-        else
-          wallet
-        end
-
-      @wallet_file =
-        if @config.wallet_paths.key?(@wallet)
-          "#{@config.wallet_paths[@wallet]}/#{@wallet}.mpw"
-        else
-          "#{@config.wallet_dir}/#{@wallet}.mpw"
-        end
-    end
-
-    # Add a new public key
-    # @param key [String] key name or key file to add
-    def add_key(key)
-      @mpw.add_key(key)
-      @mpw.write_data
-
-      puts I18n.t('form.add_key.valid').to_s.green
-    rescue => e
-      puts "#{I18n.t('display.error')} #13: #{e}".red
-    end
-
-    # Add new public key
-    # @param key [String] key name to delete
-    def delete_key(key)
-      @mpw.delete_key(key)
-      @mpw.write_data
-
-      puts I18n.t('form.delete_key.valid').to_s.green
-    rescue => e
-      puts "#{I18n.t('display.error')} #15: #{e}".red
-    end
-
-    # Text editor interface
-    # @param template_name [String] template name
-    # @param item [Item] the item to edit
-    # @param password [Boolean] disable field password
-    # @return [Hash] the values for an item
-    def text_editor(template_name, password = false, item = nil, **options)
-      editor        = ENV['EDITOR'] || 'nano'
-      opts          = {}
-      template_file = "#{File.expand_path('../../../templates', __FILE__)}/#{template_name}.erb"
-      template      = ERB.new(IO.read(template_file))
-
-      Dir.mktmpdir do |dir|
-        tmp_file = "#{dir}/#{template_name}.yml"
-
-        File.open(tmp_file, 'w') do |f|
-          f << template.result(binding)
-        end
-
-        system("#{editor} #{tmp_file}")
-
-        opts = YAML.load_file(tmp_file)
-      end
-
-      opts.delete_if { |_, v| v.to_s.empty? }
-
-      opts.each do |k, v|
-        options[k.to_sym] = v
-      end
-
-      options
-    end
-
-    # Form to add a new item
-    # @param password [Boolean] generate a random password
-    # @param text_editor [Boolean] enable text editor mode
-    # @param values [Hash] multiples value to set the item
-    def add(password = false, text_editor = false, **values)
-      options            = text_editor('add_form', password, nil, values) if text_editor
-      item               = Item.new(options)
-      options[:password] = MPW.password(@config.password) if password
-
-      @mpw.add(item)
-      @mpw.set_password(item.id, options[:password]) if options.key?(:password)
-      @mpw.set_otp_key(item.id, options[:otp_key])   if options.key?(:otp_key)
-      @mpw.write_data
-
-      puts I18n.t('form.add_item.valid').to_s.green
-    rescue => e
-      puts "#{I18n.t('display.error')} #13: #{e}".red
-    end
-
-    # Update an item
-    # @param password [Boolean] generate a random password
-    # @param text_editor [Boolean] enable text editor mode
-    # @param options [Hash] the options to search
-    # @param values [Hash] multiples value to set the item
-    def update(password = false, text_editor = false, options = {}, **values)
-      items = @mpw.list(options)
-
-      if items.empty?
-        puts I18n.t('display.nothing')
-      else
-        table_items(items) if items.length > 1
-
-        item              = get_item(items)
-        values            = text_editor('update_form', password, item, values) if text_editor
-        values[:password] = MPW.password(@config.password) if password
-
-        item.update(values)
-        @mpw.set_password(item.id, values[:password]) if values.key?(:password)
-        @mpw.set_otp_key(item.id, values[:otp_key])   if values.key?(:otp_key)
-        @mpw.write_data
-
-        puts I18n.t('form.update_item.valid').to_s.green
-      end
-    rescue => e
-      puts "#{I18n.t('display.error')} #14: #{e}".red
-    end
-
-    # Remove an item
-    # @param options [Hash] the options to search
-    def delete(**options)
-      items = @mpw.list(options)
-
-      if items.empty?
-        puts I18n.t('display.nothing')
-      else
-        table_items(items)
-
-        item    = get_item(items)
-        confirm = ask("#{I18n.t('form.delete_item.ask')} (y/N) ").to_s
-
-        return unless confirm =~ /^(y|yes|YES|Yes|Y)$/
-
-        item.delete
-        @mpw.write_data
-
-        puts I18n.t('form.delete_item.valid').to_s.green
-      end
-    rescue => e
-      puts "#{I18n.t('display.error')} #16: #{e}".red
-    end
-
-    # Copy a password, otp, login
-    # @param clipboard [Boolean] enable clipboard
-    # @param options [Hash] the options to search
-    def copy(clipboard = true, **options)
-      items = @mpw.list(options)
-
-      if items.empty?
-        puts I18n.t('display.nothing')
-      else
-        table_items(items)
-
-        item = get_item(items)
-        clipboard(item, clipboard)
-      end
-    rescue => e
-      puts "#{I18n.t('display.error')} #14: #{e}".red
-    end
-
-    # Export the items in an yaml file
-    # @param file [String] the path of destination file
-    # @param options [Hash] options to search
-    def export(file, options)
-      file  = 'export-mpw.yml' if file.to_s.empty?
-      items = @mpw.list(options)
-      data  = {}
-
-      items.each do |item|
-        data.merge!(
-          item.id => {
-            'comment'   => item.comment,
-            'created'   => item.created,
-            'group'     => item.group,
-            'last_edit' => item.last_edit,
-            'otp_key'   => @mpw.get_otp_key(item.id),
-            'password'  => @mpw.get_password(item.id),
-            'url'       => item.url,
-            'user'      => item.user
-          }
-        )
-      end
-
-      File.open(file, 'w') { |f| f << data.to_yaml }
-
-      puts I18n.t('form.export.valid', file: file).to_s.green
-    rescue => e
-      puts "#{I18n.t('display.error')} #17: #{e}".red
-    end
-
-    # Import items from an yaml file
-    # @param file [String] path of import file
-    # @param format [String] the software import file format
-    def import(file, format = 'mpw')
-      raise I18n.t('form.import.file_empty')     if file.to_s.empty?
-      raise I18n.t('form.import.file_not_exist') unless File.exist?(file)
-
-      begin
-        require "mpw/import/#{format}"
-      rescue LoadError
-        raise I18n.t('form.import.format_unknown', file_format: format)
-      end
-
-      Import.send(format, file).each_value do |row|
-        item = Item.new(
-          comment:  row['comment'],
-          group:    row['group'],
-          url:      row['url'],
-          user:     row['user']
-        )
-
-        next if item.empty?
-
-        @mpw.add(item)
-        @mpw.set_password(item.id, row['password']) unless row['password'].to_s.empty?
-        @mpw.set_otp_key(item.id, row['otp_key'])   unless row['otp_key'].to_s.empty?
-      end
-
-      @mpw.write_data
-
-      puts I18n.t('form.import.valid').to_s.green
-    rescue => e
-      puts "#{I18n.t('display.error')} #18: #{e}".red
-    end
-  end
+class Cli
+
+	# Constructor
+	# @args: config -> the config
+	#        sync -> boolean for sync or not
+	def initialize(config, sync=true)
+		@config    = config
+		@sync      = sync
+	end
+
+	# Change a parameter int the config after init
+	# @args: options -> param to change
+	def set_config(options)
+		gpg_key    = options[:gpg_key]    || @config.key
+		lang       = options[:lang]       || @config.lang
+		wallet_dir = options[:wallet_dir] || @config.wallet_dir
+		gpg_exe    = options[:gpg_exe]    || @config.gpg_exe
+
+		@config.setup(gpg_key, lang, wallet_dir, gpg_exe)
+	rescue Exception => e
+		puts "#{I18n.t('display.error')} #15: #{e}".red
+		exit 2
+	end
+
+	# Create a new config file
+	# @args: options -> set param
+	def setup(options)
+		lang = options[:lang] || Locale::Tag.parse(ENV['LANG']).to_simple.to_s[0..1]
+
+		I18n.locale = lang.to_sym
+
+		@config.setup(options[:gpg_key], lang, options[:wallet_dir], options[:gpg_exe])
+
+		load_config
+
+		puts "#{I18n.t('form.setup_config.valid')}".green
+	rescue Exception => e
+		puts "#{I18n.t('display.error')} #8: #{e}".red
+		exit 2
+	end
+	
+	# Setup a new GPG key
+	# @args: gpg_key -> the key name
+	def setup_gpg_key(gpg_key)
+		return if @config.check_gpg_key?
+
+		password = ask(I18n.t('form.setup_gpg_key.password')) {|q| q.echo = false}
+		confirm  = ask(I18n.t('form.setup_gpg_key.confirm_password')) {|q| q.echo = false}
+
+		if password != confirm 
+			raise I18n.t('form.setup_gpg_key.error_password')
+		end
+
+		@password = password.to_s 
+
+		puts I18n.t('form.setup_gpg_key.wait')
+		
+		@config.setup_gpg_key(@password, gpg_key)
+
+		puts "#{I18n.t('form.setup_gpg_key.valid')}".green
+	rescue Exception => e
+		puts "#{I18n.t('display.error')} #8: #{e}".red
+		exit 2
+	end
+
+	# Setup wallet config for sync
+	# @args: options -> value to change
+	def setup_wallet_config(options={})
+		if not options[:password].nil?
+			options[:password] = ask(I18n.t('form.setup_wallet.password')) {|q| q.echo = false}
+		end
+
+		#wallet_file = wallet.nil? ? "#{@config.wallet_dir}/default.mpw" : "#{@config.wallet_dir}/#{wallet}.mpw"
+
+		@mpw = MPW.new(@config.key, @wallet_file, @password, @config.gpg_exe)
+		@mpw.read_data
+		@mpw.set_config(options)
+		@mpw.write_data
+
+		puts "#{I18n.t('form.setup_wallet.valid')}".green
+	rescue Exception => e
+		puts "#{I18n.t('display.error')} #10: #{e}".red
+		exit 2
+	end
+	
+	# Load config
+	def load_config
+		@config.load_config
+
+	rescue Exception => e
+		puts "#{I18n.t('display.error')} #10: #{e}".red
+		exit 2
+	end
+
+	# Request the GPG password and decrypt the file
+	def decrypt
+		if not defined?(@mpw)
+			@password = ask(I18n.t('display.gpg_password')) {|q| q.echo = false}
+			@mpw      = MPW.new(@config.key, @wallet_file, @password, @config.gpg_exe)
+		end
+
+		@mpw.read_data
+		@mpw.sync if @sync
+	rescue Exception => e
+		puts "#{I18n.t('display.error')} #11: #{e}".red
+		exit 2
+	end
+
+	# Format items on a table
+	def table(items=[])
+		group        = '.'
+		i            = 1
+		length_total = 10
+		data         = { id:       { length: 3,  color: 'cyan' },
+		                 host:     { length: 9,  color: 'yellow' },
+		                 user:     { length: 7,  color: 'green' },
+		                 protocol: { length: 9,  color: 'white' },
+		                 port:     { length: 5,  color: 'white' },
+		                 otp:      { length: 4,  color: 'white' },
+		                 comment:  { length: 14, color: 'magenta' },
+		               }
+
+		items.each do |item|
+			data.each do |k, v|
+				next if k == :id or k == :otp
+
+				v[:length] = item.send(k.to_s).length + 3 if item.send(k.to_s).to_s.length >= v[:length]
+			end
+		end
+		data[:id][:length]  = items.length.to_s.length + 2 if items.length.to_s.length > data[:id][:length]
+		
+		data.each_value { |v| length_total += v[:length] }
+		items.sort!     { |a, b| a.group.to_s.downcase <=> b.group.to_s.downcase }
+
+		items.each do |item|
+			if group != item.group
+				group = item.group
+
+				if group.to_s.empty?
+					puts "\n#{I18n.t('display.no_group')}".red
+				else
+					puts "\n#{group}".red
+				end
+
+				print ' '
+				length_total.times { print '=' }
+				print "\n "
+				data.each do |k, v|
+					case k
+					when :id
+						print ' ID'
+					when :otp
+						print '| OTP'
+					else
+						print "| #{k.to_s.capitalize}"
+					end
+
+					(v[:length] - k.to_s.length).times { print ' ' }
+				end
+				print "\n "
+				length_total.times { print '=' }
+				print "\n"
+			end
+
+			print "  #{i}".send(data[:id][:color])
+			(data[:id][:length] - i.to_s.length).times { print ' ' }
+			data.each do |k, v|
+				next if k == :id
+
+				if k == :otp
+					print '| '
+					if item.otp;  print ' X  ' else 4.times { print ' ' } end
+
+					next
+				end
+
+				print '| '
+				print "#{item.send(k.to_s)}".send(v[:color])
+				(v[:length] - item.send(k.to_s).to_s.length).times { print ' ' }
+			end
+			print "\n"
+
+			i += 1
+		end
+
+		print "\n"
+	end
+
+	# Display the query's result
+	# @args: options -> the option to search
+	def list(options={})
+		result = @mpw.list(options)
+
+		if result.length == 0
+			puts I18n.t('display.nothing')
+
+		else
+			table(result)
+		end
+	end
+
+	# Get an item when multiple choice
+	# @args: items -> array of items
+	# @rtrn: item
+	def get_item(items)
+		return items[0] if items.length == 1
+
+		items.sort! { |a,b| a.group.to_s.downcase <=> b.group.to_s.downcase }
+		choice = ask(I18n.t('form.select')).to_i
+
+		if choice >= 1 and choice <= items.length
+			return items[choice-1]
+		else
+			return nil
+		end
+	end
+
+	# Copy in clipboard the login and password
+	# @args: item -> the item
+	#        clipboard -> enable clipboard
+	def clipboard(item, clipboard=true)
+		pid = nil
+
+		# Security: force quit after 90s
+		Thread.new do
+			sleep 90
+			exit
+		end
+		
+		while true
+			choice = ask(I18n.t('form.clipboard.choice')).to_s
+			
+			case choice
+			when 'q', 'quit'
+				break
+
+			when 'l', 'login'
+				if clipboard
+					Clipboard.copy(item.user)
+					puts I18n.t('form.clipboard.login').green
+				else
+					puts item.user
+				end
+
+			when 'p', 'password'
+				if clipboard
+					Clipboard.copy(@mpw.get_password(item.id))
+					puts I18n.t('form.clipboard.password').yellow
+
+					Thread.new do
+						sleep 30
+
+						Clipboard.clear
+					end
+				else
+					puts @mpw.get_password(item.id)
+				end
+
+			when 'o', 'otp'
+				if clipboard
+					Clipboard.copy(@mpw.get_otp_code(item.id))
+				else
+					puts @mpw.get_otp_code(item.id)
+				end
+				puts I18n.t('form.clipboard.otp', time: @mpw.get_otp_remaining_time).yellow
+
+			else
+				puts "----- #{I18n.t('form.clipboard.help.name')} -----".cyan
+				puts I18n.t('form.clipboard.help.login')
+				puts I18n.t('form.clipboard.help.password')
+				puts I18n.t('form.clipboard.help.otp_code')
+				puts I18n.t('form.clipboard.help.quit')
+				next
+			end
+		end
+
+		Clipboard.clear
+	rescue SystemExit, Interrupt
+		Clipboard.clear
+	end
+
+	# List all wallets
+	def list_wallet
+		wallets = Dir.glob("#{@config.wallet_dir}/*.mpw")
+
+		wallets.each do |wallet|
+			puts File.basename(wallet, '.mpw')
+		end
+	end
+
+	# Display the wallet
+	# @args: wallet -> the wallet name
+	def get_wallet(wallet=nil)
+		if wallet.to_s.empty?
+			wallets = Dir.glob("#{@config.wallet_dir}/*.mpw")
+
+			if wallets.length == 1
+				@wallet_file = wallets[0]
+			else
+				@wallet_file = "#{@config.wallet_dir}/default.mpw"
+			end
+		else
+			@wallet_file = "#{@config.wallet_dir}/#{wallet}.mpw"
+		end
+	end
+
+	# Add a new public key
+	# args: key -> the key name to add
+	#       file -> gpg public file to import
+	def add_key(key, file=nil)
+		@mpw.add_key(key, file)
+		@mpw.write_data
+		@mpw.sync(true) if @sync
+
+		puts "#{I18n.t('form.add_key.valid')}".green
+	rescue Exception => e
+		puts "#{I18n.t('display.error')} #13: #{e}".red
+	end
+
+	# Add new public key
+	# args: key -> the key name to delete
+	def delete_key(key)
+		@mpw.delete_key(key)
+		@mpw.write_data
+		@mpw.sync(true) if @sync
+
+		puts "#{I18n.t('form.delete_key.valid')}".green
+	rescue Exception => e
+		puts "#{I18n.t('display.error')} #15: #{e}".red
+	end
+
+	# Text editor interface
+	# @args: template -> template name
+	#        item -> the item to edit
+	#        password -> disable field password
+	def text_editor(template_name, item=nil, password=false)
+		editor        = ENV['EDITOR'] || 'nano'
+		options       = {}
+		opts          = {}
+		template_file = "#{File.expand_path('../../../templates', __FILE__)}/#{template_name}.erb"
+		template      = ERB.new(IO.read(template_file))
+
+		Dir.mktmpdir do |dir|
+			tmp_file = "#{dir}/#{template_name}.yml"
+
+			File.open(tmp_file, 'w') do |f|
+				f << template.result(binding)
+			end
+
+			system("#{editor} #{tmp_file}")
+
+			opts = YAML::load_file(tmp_file)
+		end
+
+		opts.delete_if { |k,v| v.to_s.empty? }
+
+		opts.each do |k,v|
+			options[k.to_sym] = v
+		end
+
+		return options
+	end
+
+	# Form to add a new item
+	# @args: password -> generate a random password
+	def add(password=false)
+		options = text_editor('add_form', nil, password)	
+		item    = Item.new(options)
+
+		if password
+			options[:password] = MPW::password(length: 24)
+		end
+		
+		@mpw.add(item)
+		@mpw.set_password(item.id, options[:password]) if options.has_key?(:password)
+		@mpw.set_otp_key(item.id, options[:otp_key])   if options.has_key?(:otp_key)
+		@mpw.write_data
+		@mpw.sync(true) if @sync
+
+		puts "#{I18n.t('form.add_item.valid')}".green
+	rescue Exception => e
+		puts "#{I18n.t('display.error')} #13: #{e}".red
+	end
+
+	# Update an item
+	# @args: options -> the option to search
+	def update(options={})
+		items = @mpw.list(options)
+		
+		if items.length == 0
+			puts "#{I18n.t('display.warning')}: #{I18n.t('warning.select')}".yellow
+		else
+			table(items) if items.length > 1
+
+			item    = get_item(items)
+			options = text_editor('update_form', item)
+
+			item.update(options)
+			@mpw.set_password(item.id, options[:password]) if options.has_key?(:password)
+			@mpw.set_otp_key(item.id, options[:otp_key])   if options.has_key?(:otp_key)
+			@mpw.write_data
+			@mpw.sync(true) if @sync
+
+			puts "#{I18n.t('form.update_item.valid')}".green
+		end
+	rescue Exception => e
+		puts "#{I18n.t('display.error')} #14: #{e}".red
+	end
+
+	# Remove an item
+	# @args: options -> the option to search
+	def delete(options={})
+		items = @mpw.list(options)
+		
+		if items.length == 0
+			puts "#{I18n.t('display.warning')}: #{I18n.t('warning.select')}".yellow
+		else
+			table(items)
+
+			item    = get_item(items)
+			confirm = ask("#{I18n.t('form.delete_item.ask')} (y/N) ").to_s
+	
+			if not confirm =~ /^(y|yes|YES|Yes|Y)$/
+				return false
+			end
+	
+			item.delete
+			@mpw.write_data
+			@mpw.sync(true) if @sync
+	
+			puts "#{I18n.t('form.delete_item.valid')}".green
+		end
+	rescue Exception => e
+		puts "#{I18n.t('display.error')} #16: #{e}".red
+	end
+
+	# Copy a password, otp, login
+	# @args: clipboard -> enable clipboard
+	#        options -> the option to search
+	def copy(clipboard=true, options={})
+		items = @mpw.list(options)
+		
+		if items.length == 0
+			puts "#{I18n.t('display.warning')}: #{I18n.t('warning.select')}".yellow
+		else
+			table(items)
+
+			item = get_item(items)
+			clipboard(item, clipboard)
+		end
+	rescue Exception => e
+		puts "#{I18n.t('display.error')} #14: #{e}".red
+	end
+
+	# Export the items in a CSV file
+	# @args: file -> the destination file
+	#        options -> option to search
+	def export(file, options)
+		file  = 'export-mpw.yml' if file.to_s.empty?
+		items = @mpw.list(options)
+		data  = {}
+		i     = 1
+
+		items.each do |item|
+			data.merge!(i => { 'host'      => item.host,
+			                   'user'      => item.user,
+			                   'group'     => item.group,
+			                   'password'  => @mpw.get_password(item.id),
+			                   'protocol'  => item.protocol,
+			                   'port'      => item.port,
+			                   'otp_key'   => @mpw.get_otp_key(item.id),
+			                   'comment'   => item.comment,
+			                   'last_edit' => item.last_edit,
+			                   'created'   => item.created,
+			                 }
+			            )
+
+			i += 1
+		end
+
+		File.open(file, 'w') {|f| f << data.to_yaml}
+
+		puts "#{I18n.t('export.valid', file)}".green
+	rescue Exception => e
+		puts "#{I18n.t('display.error')} #17: #{e}".red
+	end
+
+	# Import items from a YAML file
+	# @args: file -> the import file
+	def import(file)
+		raise I18n.t('import.file_empty')     if file.to_s.empty?
+		raise I18n.t('import.file_not_exist') if not File.exist?(file)
+
+		YAML::load_file(file).each_value do |row|
+
+			item = Item.new(group:    row['group'],
+			                host:     row['host'],
+			                protocol: row['protocol'],
+			                user:     row['user'],
+			                port:     row['port'],
+			                comment:  row['comment'],
+			               )
+
+			next if item.empty?
+
+			@mpw.add(item)
+			@mpw.set_password(item.id, row['password']) if not row['password'].to_s.empty?
+			@mpw.set_otp_key(item.id, row['otp_key'])   if not row['otp_key'].to_s.empty?
+		end
+
+		@mpw.write_data
+
+		puts "#{I18n.t('form.import.valid')}".green
+	rescue Exception => e
+		puts "#{I18n.t('display.error')} #18: #{e}".red
+	end
+end
 end
diff --git a/lib/mpw/config.rb b/lib/mpw/config.rb
index f3974b5..e8911fa 100644
--- a/lib/mpw/config.rb
+++ b/lib/mpw/config.rb
@@ -1,190 +1,144 @@
-#
-# Copyright:: 2013, Adrien Waksberg
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
+#!/usr/bin/ruby
+# MPW is a software to crypt and manage your passwords
+# Copyright (C) 2016  Adrien Waksberg <mpw@yae.im>
+# 
+# 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 2
+# 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 require 'gpgme'
 require 'yaml'
 require 'i18n'
 require 'fileutils'
-
+	
 module MPW
-  class Config
-    attr_accessor :error_msg
+class Config
+	
+	attr_accessor :error_msg
 
-    attr_accessor :gpg_key
-    attr_accessor :lang
-    attr_accessor :config_dir
-    attr_accessor :default_wallet
-    attr_accessor :wallet_dir
-    attr_accessor :wallet_paths
-    attr_accessor :gpg_exe
-    attr_accessor :password
-    attr_accessor :pinmode
+	attr_accessor :key
+	attr_accessor :lang
+	attr_accessor :config_dir
+	attr_accessor :wallet_dir
+	attr_accessor :gpg_exe
 
-    # @param config_file [String] path of config file
-    def initialize(config_file = nil)
-      @config_file = config_file
-      @config_dir  =
-        if RUBY_PLATFORM =~ /darwin/
-          "#{Dir.home}/Library/Preferences/mpw"
-        elsif RUBY_PLATFORM =~ /cygwin|mswin|mingw|bccwin|wince|emx/
-          "#{Dir.home}/AppData/Local/mpw"
-        else
-          "#{Dir.home}/.config/mpw"
-        end
+	# Constructor
+	# @args: config_file -> the specify config file
+	def initialize(config_file=nil)
+		@config_file = config_file
 
-      @config_file = "#{@config_dir}/mpw.cfg" if @config_file.to_s.empty?
-    end
+		if /darwin/ =~ RUBY_PLATFORM
+			@config_dir = "#{Dir.home}/Library/Preferences/mpw"
+		elsif /cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM
+			@config_dir = "#{Dir.home}/AppData/Local/mpw"
+		else 
+			@config_dir = "#{Dir.home}/.config/mpw"
+		end
+		
+		if @config_file.nil? or @config_file.empty?
+			@config_file = "#{@config_dir}/mpw.cfg"
+		end
+	end
 
-    # Create a new config file
-    # @param options [Hash] the value to set the config file
-    def setup(**options)
-      gpg_key        = options[:gpg_key]        || @gpg_key
-      lang           = options[:lang]           || @lang
-      wallet_dir     = options[:wallet_dir]     || @wallet_dir
-      default_wallet = options[:default_wallet] || @default_wallet
-      gpg_exe        = options[:gpg_exe]        || @gpg_exe
-      pinmode        = options.key?(:pinmode) ? options[:pinmode] : @pinmode
-      password       = {
-        numeric: true,
-        alpha:   true,
-        special: false,
-        length:  16
-      }
+	# Create a new config file
+	# @args: key -> the gpg key to encrypt
+	#        lang -> the software language
+	#        wallet_dir -> the  directory where are the wallets password
+	#        gpg_exe -> the  path of gpg executable
+	# @rtrn: true if le config file is create
+	def setup(key, lang, wallet_dir, gpg_exe)
+		if not key =~ /[a-zA-Z0-9.-_]+\@[a-zA-Z0-9]+\.[a-zA-Z]+/
+			raise I18n.t('error.config.key_bad_format')
+		end
 
-      %w[numeric special alpha length].each do |k|
-        if options.key?("pwd_#{k}".to_sym)
-          password[k.to_sym] = options["pwd_#{k}".to_sym]
-        elsif !@password.nil? && @password.key?(k.to_sym)
-          password[k.to_sym] = @password[k.to_sym]
-        end
-      end
+		if wallet_dir.to_s.empty?
+			wallet_dir = "#{@config_dir}/wallets"
+		end
 
-      unless gpg_key =~ /[a-zA-Z0-9.-_]+\@[a-zA-Z0-9]+\.[a-zA-Z]+/
-        raise I18n.t('error.config.key_bad_format')
-      end
+		config = { 'key'        => key,
+		           'lang'       => lang,
+		           'wallet_dir' => wallet_dir,
+		           'gpg_exe'    => gpg_exe,
+		         }
 
-      wallet_dir = "#{@config_dir}/wallets" if wallet_dir.to_s.empty?
-      config     = { 'gpg_key'        => gpg_key,
-                     'lang'           => lang,
-                     'wallet_dir'     => wallet_dir,
-                     'default_wallet' => default_wallet,
-                     'gpg_exe'        => gpg_exe,
-                     'password'       => password,
-                     'pinmode'        => pinmode,
-                     'wallet_paths'   => @wallet_paths }
+		FileUtils.mkdir_p(@config_dir, mode: 0700)
+		FileUtils.mkdir_p(wallet_dir,  mode: 0700)
 
-      FileUtils.mkdir_p(@config_dir, mode: 0700)
-      FileUtils.mkdir_p(wallet_dir,  mode: 0700)
+		File.open(@config_file, 'w') do |file|
+			file << config.to_yaml
+		end
+		
+	rescue Exception => e 
+		raise "#{I18n.t('error.config.write')}\n#{e}"
+	end
 
-      File.open(@config_file, 'w') do |file|
-        file << config.to_yaml
-      end
-    rescue => e
-      raise "#{I18n.t('error.config.write')}\n#{e}"
-    end
+	# Setup a new gpg key
+	# @args: password -> the GPG key password
+	#        name -> the name of user
+	#        length -> length of the GPG key
+	#        expire -> the time of expire to GPG key
+	# @rtrn: true if the GPG key is create, else false
+	def setup_gpg_key(password, name, length = 4096, expire = 0)
+		if name.to_s.empty?
+			raise "#{I18n.t('error.config.genkey_gpg.name')}"
+		elsif password.to_s.empty?
+			raise "#{I18n.t('error.config.genkey_gpg.password')}"
+		end
 
-    # Setup a new gpg key
-    # @param password [String] gpg key password
-    # @param name [String] the name of user
-    # @param length [Integer] length of the gpg key
-    # @param expire [Integer] time of expire to gpg key
-    def setup_gpg_key(password, name, length = 4096, expire = 0)
-      raise I18n.t('error.config.genkey_gpg.name') if name.to_s.empty?
-      raise I18n.t('error.config.genkey_gpg.password') if password.to_s.empty?
+		param = ''
+		param << '<GnupgKeyParms format="internal">' + "\n"
+		param << "Key-Type: DSA\n"  
+		param << "Key-Length: #{length}\n"
+		param << "Subkey-Type: ELG-E\n"
+		param << "Subkey-Length: #{length}\n"
+		param << "Name-Real: #{name}\n"
+		param << "Name-Comment: #{name}\n"
+		param << "Name-Email: #{@key}\n"
+		param << "Expire-Date: #{expire}\n"
+		param << "Passphrase: #{password}\n"
+		param << "</GnupgKeyParms>\n"
 
-      param = ''
-      param << '<GnupgKeyParms format="internal">' + "\n"
-      param << "Key-Type: RSA\n"
-      param << "Key-Length: #{length}\n"
-      param << "Subkey-Type: ELG-E\n"
-      param << "Subkey-Length: #{length}\n"
-      param << "Name-Real: #{name}\n"
-      param << "Name-Comment: #{name}\n"
-      param << "Name-Email: #{@gpg_key}\n"
-      param << "Expire-Date: #{expire}\n"
-      param << "Passphrase: #{password}\n"
-      param << "</GnupgKeyParms>\n"
+		ctx = GPGME::Ctx.new
+		ctx.genkey(param, nil, nil)
+	rescue Exception => e
+		raise "#{I18n.t('error.config.genkey_gpg.exception')}\n#{e}"
+	end
 
-      ctx = GPGME::Ctx.new
-      ctx.genkey(param, nil, nil)
-    rescue => e
-      raise "#{I18n.t('error.config.genkey_gpg.exception')}\n#{e}"
-    end
+	# Load the config file
+	def load_config
+		config      = YAML::load_file(@config_file)
+		@key        = config['key']
+		@lang       = config['lang']
+		@wallet_dir = config['wallet_dir']
+		@gpg_exe    = config['gpg_exe']
 
-    # Load the config file
-    def load_config
-      config          = YAML.load_file(@config_file)
-      @gpg_key        = config['gpg_key']
-      @lang           = config['lang']
-      @wallet_dir     = config['wallet_dir']
-      @wallet_paths   = config['wallet_paths'] || {}
-      @default_wallet = config['default_wallet']
-      @gpg_exe        = config['gpg_exe']
-      @password       = config['password'] || {}
-      @pinmode        = config['pinmode'] || false
+		raise if @key.empty? or @wallet_dir.empty?
+			
+		I18n.locale = @lang.to_sym
 
-      raise if @gpg_key.empty? || @wallet_dir.empty?
+	rescue Exception => e
+		raise "#{I18n.t('error.config.load')}\n#{e}"
+	end
 
-      I18n.locale = @lang.to_sym
-    rescue => e
-      raise "#{I18n.t('error.config.load')}\n#{e}"
-    end
+	# Check if private key exist
+	# @rtrn: true if the key exist, else false
+	def check_gpg_key?
+		ctx = GPGME::Ctx.new
+		ctx.each_key(@key, true) do
+			return true
+		end
 
-    # Check if private key exist
-    # @return [Boolean] true if the key exist, else false
-    def check_gpg_key?
-      ctx = GPGME::Ctx.new
-      ctx.each_key(@gpg_key, true) do
-        return true
-      end
-
-      false
-    end
-
-    # Change the path of one wallet
-    # @param path [String]new directory path
-    # @param wallet [String] wallet name
-    def set_wallet_path(path, wallet)
-      path = @wallet_dir if path == 'default'
-      path = File.absolute_path(path)
-
-      return if path == @wallet_dir && File.exist?("#{@wallet_dir}/#{wallet}.mpw")
-      return if path == @wallet_paths[wallet]
-
-      old_wallet_file =
-        if @wallet_paths.key?(wallet)
-          "#{@wallet_paths[wallet]}/#{wallet}.mpw"
-        else
-          "#{@wallet_dir}/#{wallet}.mpw"
-        end
-
-      FileUtils.mkdir_p(path) unless Dir.exist?(path)
-      FileUtils.mv(old_wallet_file, "#{path}/#{wallet}.mpw") if File.exist?(old_wallet_file)
-
-      if path == @wallet_dir
-        @wallet_paths.delete(wallet)
-      else
-        @wallet_paths[wallet] = path
-      end
-
-      setup
-    end
-  end
+		return false
+	end
+end
 end
diff --git a/lib/mpw/import/gorilla.rb b/lib/mpw/import/gorilla.rb
deleted file mode 100644
index feffce6..0000000
--- a/lib/mpw/import/gorilla.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-#
-# Copyright:: 2013, Adrien Waksberg
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-require 'csv'
-
-module MPW
-  module Import
-    # Import an export mpw file
-    # @param file [String] the file path to import
-    def self.gorilla(file)
-      data = {}
-
-      CSV.foreach(file, headers: true) do |row|
-        id = row['uuid']
-        comment =
-          if row['title'] && row['notes']
-            "#{row['title']} #{row['notes']}"
-          elsif row['title']
-            row['title']
-          elsif row['notes']
-            row['notes']
-          end
-
-        data[id] = {
-          'comment'  => comment,
-          'group'    => row['group'],
-          'password' => row['password'],
-          'url'      => row['url'],
-          'user'     => row['user']
-        }
-      end
-
-      data
-    end
-  end
-end
diff --git a/lib/mpw/import/keepass.rb b/lib/mpw/import/keepass.rb
deleted file mode 100644
index 0b961d4..0000000
--- a/lib/mpw/import/keepass.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-#
-# Copyright:: 2013, Adrien Waksberg
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-require 'csv'
-
-module MPW
-  module Import
-    # Import an keepass2 export csv file
-    # @param file [String] the file path to import
-    def self.keepass(file)
-      data = {}
-
-      CSV.foreach(file, headers: true) do |row|
-        id = "#{row['Group']} #{row['Title']}"
-        comment =
-          if row['Title'] && row['Notes']
-            "#{row['Title']} #{row['Notes']}"
-          elsif row['Title']
-            row['Title']
-          elsif row['Notes']
-            row['Notes']
-          end
-
-        data[id] = {
-          'comment'  => comment,
-          'group'    => row['Group'],
-          'password' => row['Password'],
-          'url'      => row['URL'],
-          'user'     => row['Username']
-        }
-      end
-
-      data
-    end
-  end
-end
diff --git a/lib/mpw/import/mpw.rb b/lib/mpw/import/mpw.rb
deleted file mode 100644
index a287357..0000000
--- a/lib/mpw/import/mpw.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-#
-# Copyright:: 2013, Adrien Waksberg
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-require 'yaml'
-
-module MPW
-  module Import
-    # Import an export mpw file
-    # @param file [String] the file path to import
-    def self.mpw(file)
-      YAML.load_file(file)
-    end
-  end
-end
diff --git a/lib/mpw/import/mpw_old.rb b/lib/mpw/import/mpw_old.rb
deleted file mode 100644
index 923bf16..0000000
--- a/lib/mpw/import/mpw_old.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-#
-# Copyright:: 2013, Adrien Waksberg
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-require 'yaml'
-
-module MPW
-  module Import
-    # Import an export mpw file
-    # @param file [String] the file path to import
-    def self.mpw_old(file)
-      data = {}
-      YAML.load_file(file).each do |id, item|
-        url = ''
-        url += "#{item['protocol']}://" if item['protocol']
-        url += item['host']
-        url += ":#{item['port']}" if item['port']
-
-        data[id] = {
-          'comment'  => item['comment'],
-          'group'    => item['group'],
-          'otp'      => item['otp'],
-          'password' => item['password'],
-          'url'      => url,
-          'user'     => item['user']
-        }
-      end
-
-      data
-    end
-  end
-end
diff --git a/lib/mpw/item.rb b/lib/mpw/item.rb
index 9da60b1..4beb49f 100644
--- a/lib/mpw/item.rb
+++ b/lib/mpw/item.rb
@@ -1,108 +1,109 @@
-#
-# Copyright:: 2013, Adrien Waksberg
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
+#!/usr/bin/ruby
+# MPW is a software to crypt and manage your passwords
+# Copyright (C) 2016  Adrien Waksberg <mpw@yae.im>
+# 
+# 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 2
+# 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 require 'i18n'
-require 'uri'
-
+	
 module MPW
-  class Item
-    attr_accessor :created
-    attr_accessor :comment
-    attr_accessor :group
-    attr_accessor :host
-    attr_accessor :id
-    attr_accessor :otp
-    attr_accessor :port
-    attr_accessor :protocol
-    attr_accessor :last_edit
-    attr_accessor :url
-    attr_accessor :user
+class Item
 
-    # @param options [Hash] the option :host is required
-    def initialize(**options)
-      @host = ''
+	attr_accessor :id
+	attr_accessor :group
+	attr_accessor :host
+	attr_accessor :protocol
+	attr_accessor :user
+	attr_accessor :port
+	attr_accessor :otp
+	attr_accessor :comment
+	attr_accessor :last_edit
+	attr_accessor :last_sync
+	attr_accessor :created
 
-      if !options[:id] || !options[:created]
-        @id = generate_id
-        @created = Time.now.to_i
-      else
-        @id = options[:id]
-        @created   = options[:created]
-        @last_edit = options[:last_edit]
-        options[:no_update_last_edit] = true
-      end
+	# Constructor
+	# Create a new item
+	# @args: options -> a hash of parameter
+	# raise an error if the hash hasn't the key name 
+	def initialize(options={})
+		if not options.has_key?(:host) or options[:host].to_s.empty?
+			raise I18n.t('error.update.name_empty')
+		end
 
-      update(options)
-    end
+		if not options.has_key?(:id) or options[:id].to_s.empty? or not options.has_key?(:created) or options[:created].to_s.empty?
+			@id = generate_id
+			@created = Time.now.to_i
+		else
+			@id = options[:id]
+			@created   = options[:created]
+			@last_edit = options[:last_edit]
+			options[:no_update_last_edit] = true
+		end
 
-    # Update the item
-    # @param options [Hash]
-    def update(**options)
-      unless options[:url] || options[:comment]
-        raise I18n.t('error.update.host_and_comment_empty')
-      end
+		update(options)
+	end
 
-      if options[:url]
-        uri       = URI(options[:url])
-        @host     = uri.host   || options[:url]
-        @port     = uri.port   || nil
-        @protocol = uri.scheme || nil
-        @url      = options[:url]
-      end
+	# Update the item
+	# @args: options -> a hash of parameter
+	def update(options={})
+		if options.has_key?(:host) and options[:host].to_s.empty?
+			raise I18n.t('error.update.name_empty')
+		end
 
-      @comment   = options[:comment] if options.key?(:comment)
-      @group     = options[:group]   if options.key?(:group)
-      @last_edit = Time.now.to_i     unless options.key?(:no_update_last_edit)
-      @otp       = options[:otp]     if options.key?(:otp)
-      @user      = options[:user]    if options.key?(:user)
-    end
+		@group     = options[:group]      if options.has_key?(:group)
+		@host      = options[:host]       if options.has_key?(:host)
+		@protocol  = options[:protocol]   if options.has_key?(:protocol)
+		@user      = options[:user]       if options.has_key?(:user)
+		@port      = options[:port].to_i  if options.has_key?(:port) and not options[:port].to_s.empty?
+		@otp       = options[:otp]        if options.has_key?(:otp)
+		@comment   = options[:comment]    if options.has_key?(:comment)
+		@last_edit = Time.now.to_i        if not options.has_key?(:no_update_last_edit)
+	end
 
-    # Delete all data
-    def delete
-      @id        = nil
-      @comment   = nil
-      @created   = nil
-      @group     = nil
-      @host      = nil
-      @last_edit = nil
-      @otp       = nil
-      @port      = nil
-      @protocol  = nil
-      @url       = nil
-      @user      = nil
-    end
+	# Update last_sync
+	def set_last_sync
+		@last_sync = Time.now.to_i
+	end
 
-    def empty?
-      @id.to_s.empty?
-    end
+	# Delete all data
+	def delete
+		@id        = nil
+		@group     = nil
+		@host      = nil
+		@protocol  = nil
+		@user      = nil
+		@port      = nil
+		@otp       = nil
+		@comment   = nil
+		@created   = nil
+		@last_edit = nil
+		@last_sync = nil
+	end
 
-    def nil?
-      false
-    end
+	def empty?
+		return @id.to_s.empty?
+	end
 
-    private
+	def nil?
+		return false
+	end
 
-    # Generate an random id
-    # @return [String] random string
-    def generate_id
-      [*('A'..'Z'), *('a'..'z'), *('0'..'9')].sample(16).join
-    end
-  end
+	# Generate an random id
+	private
+	def generate_id
+		return ([*('A'..'Z'),*('a'..'z'),*('0'..'9')]).sample(16).join
+	end 
+end
 end
diff --git a/lib/mpw/mpw.rb b/lib/mpw/mpw.rb
index 191caae..533077e 100644
--- a/lib/mpw/mpw.rb
+++ b/lib/mpw/mpw.rb
@@ -1,22 +1,20 @@
-#
-# Copyright:: 2013, Adrien Waksberg
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
+#!/usr/bin/ruby
+# MPW is a software to crypt and manage your passwords
+# Copyright (C) 2016  Adrien Waksberg <mpw@yae.im>
+# 
+# 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 2
+# 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 require 'rubygems/package'
 require 'gpgme'
@@ -24,328 +22,454 @@ require 'i18n'
 require 'yaml'
 require 'rotp'
 require 'mpw/item'
-
+	
 module MPW
-  class MPW
-    # @param key [String] gpg key name
-    # @param wallet_file [String] path of the wallet file
-    # @param gpg_pass [String] password of the gpg key
-    # @param gpg_exe [String] path of the gpg executable
-    # @param pinmode [Boolean] enable the gpg pinmode
-    def initialize(key, wallet_file, gpg_pass = nil, gpg_exe = nil, pinmode = false)
-      @key         = key
-      @gpg_pass    = gpg_pass
-      @gpg_exe     = gpg_exe
-      @wallet_file = wallet_file
-      @pinmode     = pinmode
+class MPW
 
-      GPGME::Engine.set_info(GPGME::PROTOCOL_OpenPGP, @gpg_exe, "#{Dir.home}/.gnupg") unless @gpg_exe.to_s.empty?
-    end
+	# Constructor
+	def initialize(key, wallet_file, gpg_pass=nil, gpg_exe=nil)
+		@key         = key
+		@gpg_pass    = gpg_pass
+		@gpg_exe     = gpg_exe
+		@wallet_file = wallet_file
 
-    # Read mpw file
-    def read_data
-      @data      = []
-      @keys      = {}
-      @passwords = {}
-      @otp_keys  = {}
+		if not @gpg_exe.to_s.empty?
+			GPGME::Engine.set_info(GPGME::PROTOCOL_OpenPGP, @gpg_exe, "#{Dir.home}/.gnupg")
+		end
+	end
 
-      data       = nil
+	# Read mpw file
+	def read_data
+		@config    = {}
+		@data      = []
+		@keys      = {}
+		@passwords = {}
+		@otp_keys  = {}
 
-      return unless File.exist?(@wallet_file)
+		data       = nil
 
-      Gem::Package::TarReader.new(File.open(@wallet_file)) do |tar|
-        tar.each do |f|
-          case f.full_name
-          when 'wallet/meta.gpg'
-            data = decrypt(f.read)
+		return if not File.exists?(@wallet_file)
 
-          when %r{^wallet/keys/(?<key>.+)\.pub$}
-            key = Regexp.last_match('key')
+		Gem::Package::TarReader.new(File.open(@wallet_file)) do |tar|
+			tar.each do |f| 
+				case f.full_name
+					when 'wallet/config.gpg'
+						@config = YAML.load(decrypt(f.read))
 
-            if GPGME::Key.find(:public, key).empty?
-              GPGME::Key.import(f.read, armor: true)
-            end
+					when 'wallet/meta.gpg'
+						data = decrypt(f.read)
 
-            @keys[key] = f.read
+					when /^wallet\/keys\/(?<key>.+)\.pub$/
+						key = Regexp.last_match('key')
 
-          when %r{^wallet/passwords/(?<id>[a-zA-Z0-9]+)\.gpg$}
-            @passwords[Regexp.last_match('id')] = f.read
+						if GPGME::Key.find(:public, key).length == 0
+							GPGME::Key.import(f.read, armor: true)
+						end
 
-          when %r{^wallet/otp_keys/(?<id>[a-zA-Z0-9]+)\.gpg$}
-            @otp_keys[Regexp.last_match('id')] = f.read
+						@keys[key] = f.read
 
-          else
-            next
-          end
-        end
-      end
+					when /^wallet\/passwords\/(?<id>[a-zA-Z0-9]+)\.gpg$/
+						@passwords[Regexp.last_match('id')] = f.read
 
-      unless data.to_s.empty?
-        YAML.safe_load(data).each_value do |d|
-          @data.push(
-            Item.new(
-              id:        d['id'],
-              group:     d['group'],
-              user:      d['user'],
-              url:       d['url'],
-              otp:       @otp_keys.key?(d['id']),
-              comment:   d['comment'],
-              last_edit: d['last_edit'],
-              created:   d['created']
-            )
-          )
-        end
-      end
+					when /^wallet\/otp_keys\/(?<id>[a-zA-Z0-9]+)\.gpg$/
+						@otp_keys[Regexp.last_match('id')] = f.read
 
-      add_key(@key) unless @keys.key?(@key)
-    rescue => e
-      raise "#{I18n.t('error.mpw_file.read_data')}\n#{e}"
-    end
+					else
+						next
+				end
+			end
+		end
 
-    # Encrypt all data in tarball
-    def write_data
-      data     = {}
-      tmp_file = "#{@wallet_file}.tmp"
+		if not data.nil? and not data.empty?
+			YAML.load(data).each_value do |d|
+				@data.push(Item.new(id:        d['id'],
+				                    group:     d['group'],
+				                    host:      d['host'],
+				                    protocol:  d['protocol'],
+				                    user:      d['user'],
+				                    port:      d['port'],
+				                    otp:       @otp_keys.has_key?(d['id']),
+				                    comment:   d['comment'],
+				                    last_edit: d['last_edit'],
+				                    created:   d['created'],
+				                   )
+				          )
+			end
+		end
 
-      @data.each do |item|
-        next if item.empty?
+		add_key(@key) if @keys[@key].nil?
+	rescue Exception => e
+		raise "#{I18n.t('error.mpw_file.read_data')}\n#{e}"
+	end
 
-        data.merge!(
-          item.id => {
-            'id'        => item.id,
-            'group'     => item.group,
-            'user'      => item.user,
-            'url'       => item.url,
-            'comment'   => item.comment,
-            'last_edit' => item.last_edit,
-            'created'   => item.created
-          }
-        )
-      end
+	# Encrypt a file
+	def write_data
+		data     = {}
+		tmp_file = "#{@wallet_file}.tmp"
 
-      Gem::Package::TarWriter.new(File.open(tmp_file, 'w+')) do |tar|
-        data_encrypt = encrypt(data.to_yaml)
-        tar.add_file_simple('wallet/meta.gpg', 0400, data_encrypt.length) do |io|
-          io.write(data_encrypt)
-        end
+		@data.each do |item|
+			next if item.empty?
 
-        @passwords.each do |id, password|
-          tar.add_file_simple("wallet/passwords/#{id}.gpg", 0400, password.length) do |io|
-            io.write(password)
-          end
-        end
+			data.merge!(item.id => { 'id'        => item.id,
+			                         'group'     => item.group,
+			                         'host'      => item.host,
+			                         'protocol'  => item.protocol,
+			                         'user'      => item.user,
+			                         'port'      => item.port,
+			                         'comment'   => item.comment,
+			                         'last_edit' => item.last_edit,
+			                         'created'   => item.created,
+			                       }
+			           )
+		end
 
-        @otp_keys.each do |id, key|
-          tar.add_file_simple("wallet/otp_keys/#{id}.gpg", 0400, key.length) do |io|
-            io.write(key)
-          end
-        end
+		@config['last_update'] = Time.now.to_i
 
-        @keys.each do |id, key|
-          tar.add_file_simple("wallet/keys/#{id}.pub", 0400, key.length) do |io|
-            io.write(key)
-          end
-        end
-      end
+		Gem::Package::TarWriter.new(File.open(tmp_file, 'w+')) do |tar|
+			data_encrypt = encrypt(data.to_yaml)
+			tar.add_file_simple('wallet/meta.gpg', 0400, data_encrypt.length) do |io|
+				io.write(data_encrypt)
+			end
 
-      File.rename(tmp_file, @wallet_file)
-    rescue => e
-      File.unlink(tmp_file) if File.exist?(tmp_file)
+			config = encrypt(@config.to_yaml)
+			tar.add_file_simple('wallet/config.gpg', 0400, config.length) do |io|
+				io.write(config)
+			end
 
-      raise "#{I18n.t('error.mpw_file.write_data')}\n#{e}"
-    end
+			@passwords.each do |id, password|
+				tar.add_file_simple("wallet/passwords/#{id}.gpg", 0400, password.length) do |io|
+					io.write(password)
+				end
+			end
 
-    # Get a password
-    # @param id [String] the item id
-    def get_password(id)
-      password = decrypt(@passwords[id])
+			@otp_keys.each do |id, key|
+				tar.add_file_simple("wallet/otp_keys/#{id}.gpg", 0400, key.length) do |io|
+					io.write(key)
+				end
+			end
 
-      if /^\$[a-zA-Z0-9]{4,9}::(?<password>.+)$/ =~ password
-        Regexp.last_match('password')
-      else
-        password
-      end
-    end
+			@keys.each do |id, key|
+				tar.add_file_simple("wallet/keys/#{id}.pub", 0400, key.length) do |io|
+					io.write(key)
+				end
+			end
+		end
 
-    # Set a new password for an item
-    # @param id [String] the item id
-    # @param password [String] the new password
-    def set_password(id, password)
-      salt     = MPW.password(length: Random.rand(4..9))
-      password = "$#{salt}::#{password}"
+		File.rename(tmp_file, @wallet_file)
+	rescue Exception => e
+		File.unlink(tmp_file) if File.exist?(tmp_file)
 
-      @passwords[id] = encrypt(password)
-    end
+		raise "#{I18n.t('error.mpw_file.write_data')}\n#{e}"
+	end
 
-    # Return the list of all gpg keys
-    # @return [Array] the gpg keys name
-    def list_keys
-      @keys.keys
-    end
+	# Get a password
+	# args: id -> the item id
+	def get_password(id)
+		password = decrypt(@passwords[id])
+		
+		if /^\$[a-zA-Z0-9]{4,9}::(?<password>.+)$/ =~ password
+			return Regexp.last_match('password')
+		else
+			return password
+		end
+	end
 
-    # Add a public key
-    # @param key [String] new public key file or name
-    def add_key(key)
-      if File.exist?(key)
-        data       = File.open(key).read
-        key_import = GPGME::Key.import(data, armor: true)
-        key        = GPGME::Key.get(key_import.imports[0].fpr).uids[0].email
-      else
-        data = GPGME::Key.export(key, armor: true).read
-      end
+	# Set a password
+	# args: id -> the item id
+	#       password -> the new password
+	def set_password(id, password)
+		salt     = MPW::password(length: Random.rand(4..9))
+		password = "$#{salt}::#{password}"
 
-      raise I18n.t('error.export_key') if data.to_s.empty?
+		@passwords[id] = encrypt(password)
+	end
 
-      @keys[key] = data
-      @passwords.each_key { |id| set_password(id, get_password(id)) }
-      @otp_keys.each_key { |id| set_otp_key(id, get_otp_key(id)) }
-    end
+	# Add a public key
+	# args: key ->  new public key
+	#       file -> public gpg file to import
+	def add_key(key, file=nil)
+		if not file.nil? and File.exists?(file)
+			data = File.open(file).read
+			GPGME::Key.import(data, armor: true)
+		else
+			data = GPGME::Key.export(key, armor: true).read
+		end
 
-    # Delete a public key
-    # @param key [String] public key to delete
-    def delete_key(key)
-      @keys.delete(key)
-      @passwords.each_key { |id| set_password(id, get_password(id)) }
-      @otp_keys.each_key { |id| set_otp_key(id, get_otp_key(id)) }
-    end
+		if data.to_s.empty?
+			raise I18n.t('error.export_key')
+		end
 
-    # Add a new item
-    # @param item [Item]
-    def add(item)
-      raise I18n.t('error.bad_class') unless item.instance_of?(Item)
-      raise I18n.t('error.empty')     if item.empty?
+		@keys[key] = data
+	end
 
-      @data.push(item)
-    end
+	# Delete a public key
+	# args: key ->  public key to delete
+	def delete_key(key)
+		@keys.delete(key)
+	end
 
-    # Search in some csv data
-    # @param options [Hash]
-    # @return [Array] a list with the resultat of the search
-    def list(**options)
-      result = []
+	# Set config
+	# args: config -> a hash with config options
+	def set_config(options={})
+		@config              = {} if @config.nil?
 
-      search = options[:pattern].to_s.downcase
-      group  = options[:group].to_s.downcase
+		@config['protocol']  = options[:protocol] if options.has_key?(:protocol)
+		@config['host']      = options[:host]     if options.has_key?(:host)
+		@config['port']      = options[:port]     if options.has_key?(:port)
+		@config['user']      = options[:user]     if options.has_key?(:user)
+		@config['password']  = options[:password] if options.has_key?(:password)
+		@config['path']      = options[:path]     if options.has_key?(:path)
+		@config['last_sync'] = @config['last_sync'].nil? ? 0 : @config['last_sync']
+	end
 
-      @data.each do |item|
-        next if item.empty?
-        next unless group.empty? || group.eql?(item.group.to_s.downcase)
+	# Add a new item
+	# @args: item -> Object MPW::Item
+	def add(item)
+		if not item.instance_of?(Item)
+			raise I18n.t('error.bad_class')
+		elsif item.empty?
+			raise I18n.t('error.add.empty')
+		else
+			@data.push(item)
+		end
+	end
 
-        host    = item.host.to_s.downcase
-        comment = item.comment.to_s.downcase
+	# Search in some csv data
+	# @args: options -> a hash with paramaters
+	# @rtrn: a list with the resultat of the search
+	def list(options={})
+		result = []
 
-        next unless host =~ /^.*#{search}.*$/ || comment =~ /^.*#{search}.*$/
+		search = options[:pattern].to_s.downcase
+		group  = options[:group].to_s.downcase
 
-        result.push(item)
-      end
+		@data.each do |item|
+			next if item.empty?
+			next if not group.empty? and not group.eql?(item.group.to_s.downcase)
+			
+			host    = item.host.to_s.downcase
+			comment = item.comment.to_s.downcase
 
-      result
-    end
+			if not host =~ /^.*#{search}.*$/ and not comment =~ /^.*#{search}.*$/ 
+				next
+			end
 
-    # Search an item with an id
-    # @param id [String]the id item
-    # @return [Item] an item or nil
-    def search_by_id(id)
-      @data.each do |item|
-        return item if item.id == id
-      end
+			result.push(item)
+		end
 
-      nil
-    end
+		return result
+	end
 
-    # Set a new opt key
-    # @param id [String] the item id
-    # @param key [String] the new key
-    def set_otp_key(id, key)
-      @otp_keys[id] = encrypt(key.to_s) unless key.to_s.empty?
-    end
+	# Search in some csv data
+	# @args: id -> the id item
+	# @rtrn: a row with the result of the search
+	def search_by_id(id)
+		@data.each do |item|
+			return item if item.id == id
+		end
 
-    # Get an opt key
-    # @param id [String] the item id
-    def get_otp_key(id)
-      @otp_keys.key?(id) ? decrypt(@otp_keys[id]) : nil
-    end
+		return nil
+	end
 
-    # Get an otp code
-    # @param id [String] the item id
-    # @return [String] an otp code
-    def get_otp_code(id)
-      @otp_keys.key?(id) ? ROTP::TOTP.new(decrypt(@otp_keys[id])).now : 0
-    end
+	# Get last sync
+	def get_last_sync
+		return @config['last_sync'].to_i
+	rescue
+		return 0
+	end
 
-    # Get remaining time before expire otp code
-    # @return [Integer] time in seconde
-    def get_otp_remaining_time
-      (Time.now.utc.to_i / 30 + 1) * 30 - Time.now.utc.to_i
-    end
+	# Sync data with remote file
+	# @args: force -> force the sync
+	def sync(force=false)
+		return if @config.empty? or @config['protocol'].to_s.empty?
+		return if get_last_sync + 300 > Time.now.to_i and not force
 
-    # Generate a random password
-    # @param options [Hash] :length, :special, :alpha, :numeric
-    # @return [String] a random string
-    def self.password(**options)
-      length =
-        if !options.include?(:length) || options[:length].to_i <= 0
-          8
-        elsif options[:length].to_i >= 32_768
-          32_768
-        else
-          options[:length].to_i
-        end
+		tmp_file  = "#{@wallet_file}.sync"
+		
+		case @config['protocol']
+		when 'sftp', 'scp', 'ssh'
+			require "mpw/sync/ssh"
+			sync = SyncSSH.new(@config)
+		when 'ftp'
+			require 'mpw/sync/ftp'
+			sync = SyncFTP.new(@config)
+		else
+			raise I18n.t('error.sync.unknown_type')
+		end
 
-      chars = []
-      chars += [*('!'..'?')] - [*('0'..'9')]          if options[:special]
-      chars += [*('A'..'Z'), *('a'..'z')]             if options[:alpha]
-      chars += [*('0'..'9')]                          if options[:numeric]
-      chars = [*('A'..'Z'), *('a'..'z'), *('0'..'9')] if chars.empty?
+		sync.connect
+		sync.get(tmp_file)
 
-      result = ''
-      length.times do
-        result << chars.sample
-      end
+		remote = MPW.new(@key, tmp_file, @gpg_pass, @gpg_exe)
+		remote.read_data
 
-      result
-    end
+		File.unlink(tmp_file) if File.exist?(tmp_file)
 
-    private
+		return if remote.get_last_sync == @config['last_update']
 
-    # Decrypt a gpg file
-    # @param data [String] data to decrypt
-    # @return [String] data decrypted
-    def decrypt(data)
-      return nil if data.to_s.empty?
+		if not remote.to_s.empty?
+			@data.each do |item|
+				update = false
 
-      password =
-        if /^(1\.[0-9.]+|2\.0)(\.[0-9]+)?/ =~ GPGME::Engine.info.first.version || @pinmode
-          { password: @gpg_pass }
-        else
-          { password: @gpg_pass,
-            pinentry_mode: GPGME::PINENTRY_MODE_LOOPBACK }
-        end
+				remote.list.each do |r|
+					next if item.id != r.id
 
-      crypto = GPGME::Crypto.new(armor: true)
-      crypto
-        .decrypt(data, password)
-        .read.force_encoding('utf-8')
-    rescue => e
-      raise "#{I18n.t('error.gpg_file.decrypt')}\n#{e}"
-    end
+					# Update item
+					if item.last_edit < r.last_edit
+						item.update(group:     r.group,
+						            host:      r.host,
+						            protocol:  r.protocol,
+						            user:      r.user,
+						            port:      r.port,
+						            comment:   r.comment
+						           )
+						set_password(item.id, remote.get_password(item.id))
+					end
 
-    # Encrypt a file
-    # @param data [String] data to encrypt
-    # @return [String] data encrypted
-    def encrypt(data)
-      recipients = []
-      crypto     = GPGME::Crypto.new(armor: true, always_trust: true)
+					r.delete
+					update = true
 
-      recipients.push(@key)
-      @keys.each_key do |key|
-        next if key == @key
-        recipients.push(key)
-      end
+					break
+				end
+
+				# Remove an old item
+				if not update and item.last_sync.to_i < get_last_sync and item.last_edit < get_last_sync
+					item.delete
+				end
+			end
+		end
+
+		# Add item
+		remote.list.each do |r|
+			next if r.last_edit <= get_last_sync
+
+			item = Item.new(id:        r.id,
+			                group:     r.group,
+			                host:      r.host,
+			                protocol:  r.protocol,
+			                user:      r.user,
+			                port:      r.port,
+			                comment:   r.comment,
+			                created:   r.created,
+			                last_edit: r.last_edit
+			               )
+
+			set_password(item.id, remote.get_password(item.id))
+			add(item)
+		end
+
+		remote = nil
+
+		@data.each do |item|
+			item.set_last_sync
+		end
+
+		@config['last_sync'] = Time.now.to_i
+
+		write_data
+		sync.update(@wallet_file)
+	rescue Exception => e
+		File.unlink(tmp_file) if File.exist?(tmp_file)
+
+		raise "#{I18n.t('error.sync.general')}\n#{e}"
+	end
+
+	# Set an opt key
+	# args: id -> the item id
+	#       key -> the new key
+	def set_otp_key(id, key)
+		if not key.to_s.empty?
+			@otp_keys[id] = encrypt(key.to_s)
+		end
+	end
+
+	# Get an opt key
+	# args: id -> the item id
+	#       key -> the new key
+	def get_otp_key(id)
+		if @otp_keys.has_key?(id)
+			return decrypt(@otp_keys[id])
+		else
+			return nil
+		end
+	end
+
+
+	# Get an otp code
+	# @args: id -> the item id
+	# @rtrn: an otp code
+	def get_otp_code(id)
+		if not @otp_keys.has_key?(id)
+			return 0
+		else
+			return ROTP::TOTP.new(decrypt(@otp_keys[id])).now
+		end
+	end
+
+	# Get remaining time before expire otp code
+	# @rtrn: return time in seconde
+	def get_otp_remaining_time
+		return (Time.now.utc.to_i / 30 + 1) * 30 - Time.now.utc.to_i
+	end
+
+	# Generate a random password
+	# @args: options -> :length, :special, :alpha, :numeric
+	# @rtrn: a random string
+	def self.password(options={})
+		if not options.include?(:length) or options[:length].to_i <= 0
+			length = 8
+		elsif options[:length].to_i >= 32768
+			length = 32768
+		else
+			length = options[:length].to_i
+		end
+
+		chars = []
+		chars += [*('!'..'?')] - [*('0'..'9')]        if options.include?(:special)
+		chars += [*('A'..'Z'),*('a'..'z')]            if options.include?(:alpha)
+		chars += [*('0'..'9')]                        if options.include?(:numeric)
+		chars = [*('A'..'Z'),*('a'..'z'),*('0'..'9')] if chars.empty?
+
+		result = ''
+		while length > 62 do
+			result << chars.sample(62).join
+			length -= 62
+		end
+		result << chars.sample(length).join
+
+		return result
+	end
+
+	# Decrypt a gpg file
+	# @args: data -> string to decrypt
+	private
+	def decrypt(data)
+		return nil if data.to_s.empty?
+
+		crypto = GPGME::Crypto.new(armor: true)
+		
+		return crypto.decrypt(data, password: @gpg_pass).read.force_encoding('utf-8')
+	rescue Exception => e 
+		raise "#{I18n.t('error.gpg_file.decrypt')}\n#{e}"
+	end
+
+	# Encrypt a file
+	# args: data -> string to encrypt
+	private
+	def encrypt(data)
+		recipients = []
+		crypto     = GPGME::Crypto.new(armor: true, always_trust: true)
+
+		recipients.push(@key)
+		@keys.each_key do |key|
+			next if key == @key
+			recipients.push(key)
+		end
+
+		return crypto.encrypt(data, recipients: recipients).read
+	rescue Exception => e 
+		raise "#{I18n.t('error.gpg_file.encrypt')}\n#{e}"
+	end
 
-      crypto.encrypt(data, recipients: recipients).read
-    rescue => e
-      raise "#{I18n.t('error.gpg_file.encrypt')}\n#{e}"
-    end
-  end
+end
 end
diff --git a/lib/mpw/sync/ftp.rb b/lib/mpw/sync/ftp.rb
new file mode 100644
index 0000000..d64e629
--- /dev/null
+++ b/lib/mpw/sync/ftp.rb
@@ -0,0 +1,68 @@
+#!/usr/bin/ruby
+# MPW is a software to crypt and manage your passwords
+# Copyright (C) 2016  Adrien Waksberg <mpw@yae.im>
+# 
+# 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 2
+# 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+require 'i18n'
+require 'net/ftp'
+		
+module MPW
+class FTP
+
+	# Constructor
+	# @args: config -> the config
+	def initialize(config)
+		@host      = config['host']
+		@user      = config['user']
+		@password  = config['password']
+		@path      = config['path']
+		@port      = config['port'].instance_of?(Integer) ? 22 : config['port']
+	end
+
+
+	# Connect to server
+	def connect
+		Net::FTP.open(@host) do |ftp|
+			ftp.login(@user, @password)
+			break
+		end
+	rescue Exception => e
+		raise "#{I18n.t('error.sync.connection')}\n#{e}"
+	end
+
+	# Get data on server
+	# @args: file_tmp -> the path where download the file
+	def get(file_tmp)
+		Net::FTP.open(@host) do |ftp|
+			ftp.login(@user, @password)
+			ftp.gettextfile(@path, file_tmp)
+		end
+	rescue Exception => e
+		raise "#{I18n.t('error.sync.download')}\n#{e}"
+	end
+
+	# Update the remote data
+	# @args: file_gpg -> the data to send on server
+	def update(file_gpg)
+		Net::FTP.open(@host) do |ftp|
+			ftp.login(@user, @password)
+			ftp.puttextfile(file_gpg, @path)
+		end
+	rescue Exception => e
+		raise "#{I18n.t('error.sync.upload')}\n#{e}"
+	end
+end
+end
diff --git a/lib/mpw/sync/ssh.rb b/lib/mpw/sync/ssh.rb
new file mode 100644
index 0000000..4471949
--- /dev/null
+++ b/lib/mpw/sync/ssh.rb
@@ -0,0 +1,67 @@
+#!/usr/bin/ruby
+# MPW is a software to crypt and manage your passwords
+# Copyright (C) 2016  Adrien Waksberg <mpw@yae.im>
+# 
+# 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 2
+# 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+require 'i18n'
+require 'net/ssh'
+require 'net/sftp'
+	
+module MPW
+class SyncSSH
+
+	# Constructor
+	# @args: config -> the config
+	def initialize(config)
+		@host      = config['host']
+		@user      = config['user']
+		@password  = config['password']
+		@path      = config['path']
+		@port      = config['port'].instance_of?(Integer) ? 22 : config['port']
+	end
+
+	# Connect to server
+	def connect
+		Net::SSH.start(@host, @user, password: @password, port: @port) do
+			break
+		end
+	rescue Exception => e
+		raise "#{I18n.t('error.sync.connection')}\n#{e}"
+	end
+
+	# Get data on server
+	# @args: file_tmp -> the path where download the file
+	def get(file_tmp)
+		Net::SFTP.start(@host, @user, password: @password, port: @port) do |sftp|
+			sftp.lstat(@path) do |response|
+				sftp.download!(@path, file_tmp) if response.ok?
+			end
+		end
+	rescue Exception => e
+		raise "#{I18n.t('error.sync.download')}\n#{e}"
+	end
+
+	# Update the remote data
+	# @args: file_gpg -> the data to send on server
+	def update(file_gpg)
+		Net::SFTP.start(@host, @user, password: @password, port: @port) do |sftp|
+			sftp.upload!(file_gpg, @path)
+		end
+	rescue Exception => e
+		raise "#{I18n.t('error.sync.upload')}\n#{e}"
+	end
+end
+end
diff --git a/mpw.gemspec b/mpw.gemspec
index abcb375..80d9344 100644
--- a/mpw.gemspec
+++ b/mpw.gemspec
@@ -1,3 +1,4 @@
+# coding: utf-8
 lib = File.expand_path('../lib', __FILE__)
 $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
 
@@ -11,18 +12,18 @@ Gem::Specification.new do |spec|
   spec.homepage      = 'https://github.com/nishiki/manage-password'
   spec.license       = 'GPL-2.0'
 
-  spec.files         = %x(git ls-files -z).split("\x0")
+  spec.files         = `git ls-files -z`.split("\x0")
   spec.executables   = ['mpw']
   spec.test_files    = spec.files.grep(%r{^(test|spec|features)/})
   spec.require_paths = ['lib']
 
-  spec.required_ruby_version = '>= 2.1'
-
-  spec.add_dependency 'i18n',      '~> 0.9', '>= 0.9.1'
-  spec.add_dependency 'gpgme',     '~> 2.0', '>= 2.0.14'
-  spec.add_dependency 'highline',  '~> 1.7', '>= 1.7.8'
-  spec.add_dependency 'locale',    '~> 2.1', '>= 2.1.2'
-  spec.add_dependency 'colorize',  '~> 0.8', '>= 0.8.1'
-  spec.add_dependency 'clipboard', '~> 1.1', '>= 1.1.1'
-  spec.add_dependency 'rotp',      '~> 3.3', '>= 3.3.0'
+  spec.add_dependency "i18n",      "~> 0.7", ">= 0.7.0"
+  spec.add_dependency "gpgme",     "~> 2.0", ">= 2.0.12"
+  spec.add_dependency "highline",  "~> 1.7", ">= 1.7.8"
+  spec.add_dependency "locale",    "~> 2.1", ">= 2.1.2"
+  spec.add_dependency "colorize",  "~> 0.8", ">= 0.8.1"
+  spec.add_dependency "net-ssh",   "~> 3.2", ">= 3.2.0"
+  spec.add_dependency "net-sftp",  "~> 2.1", ">= 2.1.2"
+  spec.add_dependency "clipboard", "~> 1.1", ">= 1.1.1"
+  spec.add_dependency "rotp",      "~> 3.1", ">= 3.1.0"
 end
diff --git a/templates/add_form.erb b/templates/add_form.erb
index e2aea20..c2d6d73 100644
--- a/templates/add_form.erb
+++ b/templates/add_form.erb
@@ -1,13 +1,9 @@
 ---
-# <%= I18n.t('form.add_item.url') %>
-url: <%= options[:url] %>
-# <%= I18n.t('form.add_item.login') %>
-user: <%= options[:user] %>
-# <%= I18n.t('form.add_item.group') %>
-group: <%= options[:group] %><% unless password %>
-# <%= I18n.t('form.add_item.password') %>
-password:<% end %>
-# <%= I18n.t('form.add_item.comment') %>
-comment: <%= options[:comment] %>
-# <%= I18n.t('form.add_item.otp_key') %>
-otp_key: <%= options[:otp] %>
+host:           # <%= I18n.t('form.add_item.host') %>
+user:           # <%= I18n.t('form.add_item.login') %>
+group:          # <%= I18n.t('form.add_item.group') %> 
+protocol:       # <%= I18n.t('form.add_item.protocol') %><% if not password %>
+password:       # <%= I18n.t('form.add_item.password') %><% end %>
+port:           # <%= I18n.t('form.add_item.port') %>
+comment:        # <%= I18n.t('form.add_item.comment') %>
+otp_key:        # <%= I18n.t('form.add_item.otp_key') %>
diff --git a/templates/setup_form.erb b/templates/setup_form.erb
new file mode 100644
index 0000000..f7e6d7b
--- /dev/null
+++ b/templates/setup_form.erb
@@ -0,0 +1,9 @@
+---
+# <%= I18n.t('form.setup_config.lang') %>
+language: <%= @config.lang %>
+# <%= I18n.t('form.setup_config.gpg_key') %>
+gpg_key: <%= @config.key %>
+# <%= I18n.t('form.setup_config.wallet_dir') %>
+wallet_dir: <%= @config.config_dir %>
+# <%= I18n.t('form.setup_config.gpg_exe') %>
+gpg_exe: <%= @config.gpg_exe %>
diff --git a/templates/update_form.erb b/templates/update_form.erb
index eafeb1b..f4a380b 100644
--- a/templates/update_form.erb
+++ b/templates/update_form.erb
@@ -1,13 +1,17 @@
 ---
-# <%= I18n.t('form.update_item.url') %>
-host: <%  if options[:url] %><%= options[:url] %><% else %><%= item.url %><% end %>
+# <%= I18n.t('form.update_item.host') %>
+host: <%= item.host %>
 # <%= I18n.t('form.update_item.login') %>
-user: <%  if options[:user] %><%= options[:user] %><% else %><%= item.user %><% end %><% unless password %>
+user: <%= item.user %>
 # <%= I18n.t('form.update_item.password') %>
-password: <% end %>
+password: 
 # <%= I18n.t('form.update_item.group') %>
-group: <%  if options[:group] %><%= options[:group] %><% else %><%= item.group %><% end %>
+group: <%= item.group %>
+# <%= I18n.t('form.update_item.protocol') %>
+protocol: <%= item.protocol %>
+# <%= I18n.t('form.update_item.port') %>
+port: <%= item.port %>
 # <%= I18n.t('form.update_item.otp_key') %>
-otp_key: <%  if options[:otp_key] %><%= options[:otp_key] %><% end %>
+opt_key: 
 # <%= I18n.t('form.update_item.comment') %>
-comment: <%  if options[:comment] %><%= options[:comment] %><% else %><%= item.comment %><% end %>
+comment: <%= item.comment %>
diff --git a/test/files/fixtures-import.yml b/test/files/fixtures-import.yml
deleted file mode 100644
index 8cb1ff4..0000000
--- a/test/files/fixtures-import.yml
+++ /dev/null
@@ -1,16 +0,0 @@
----
-1:
-  url: https://fric.com
-  user: 230403
-  group: Bank
-  password: 5XdiTQOubRDw9B0aJoMlcEyL
-  otp_key: 330223432
-  comment: I love my bank
-2:
-  url: https://assurance.com:443
-  user: user_2132
-  host: assurance.com
-  group: Assurance
-  password: DMyK6B3v4bWO52VzU7aTHIem
-  otp_key: 
-  comment: 
diff --git a/test/files/fixtures.yml b/test/files/fixtures.yml
index d8927cb..5651911 100644
--- a/test/files/fixtures.yml
+++ b/test/files/fixtures.yml
@@ -1,31 +1,28 @@
-add:
-  url: 'https://example.com:8080'
-  group: 'Bank'
-  host: 'example.com'
-  protocol: 'https'
-  user: 'admin'
-  password: 'VmfnCN6pPIqgRIbc'
-  port: '8080'
-  comment: 'the website'
+add_new:
+  group: 'test_group'
+  host: 'test_host'
+  protocol: 'test_protocol'
+  user: 'test_user'
+  password: 'test_password'
+  port: '42'
+  comment: 'test_comment'
 
-import:
+add_existing:
   id: 'TEST-ID-XXXXX'
-  url: 'https://gogole.com:8081/toto'
-  group: 'Cloud'
-  host: 'gogole.com'
-  protocol: 'https'
-  user: 'gg-2304'
-  password: 'TITl0kV9CDDa9sVK'
-  port: '8081'
-  comment: 'My little servers'
+  group: 'test_group_existing'
+  host: 'test_host_existing'
+  protocol: 'test_protocol_existing'
+  user: 'test_user_existing'
+  password: 'test_password_existing'
+  port: '44'
+  comment: 'test_comment_existing'
   created: 1386752948
 
 update:
-  url: 'ssh://example2.com:2222'
-  group: 'Assurance'
-  host: 'example2.com'
-  protocol: 'ssh'
-  user: 'root'
-  password: 'kbSrbv4WlMaVxaZ7'
-  port: '2222'
-  comment: 'i love ssh'
+  group: 'test_group_update'
+  host: 'test_host_update'
+  protocol: 'test_protocol_update'
+  user: 'test_user_update'
+  password: 'test_password_update'
+  port: '43'
+  comment: 'test_comment_update'
diff --git a/test/files/import-gorilla.txt b/test/files/import-gorilla.txt
deleted file mode 100644
index a5fc604..0000000
--- a/test/files/import-gorilla.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-uuid,group,title,url,user,password,notes
-49627979-e393-48c4-49ca-1cf66603238e,Bank,Fric,http://fric.com,12345,secret,money money
-49627979-e393-48c4-49ca-1cf66603238f,,My little server,server.com,secret2,
-49627979-e393-48c4-49ca-1cf66603238g,Cloud,,ssh://fric.com:4333,username,secret,bastion
diff --git a/test/files/import-keepass.txt b/test/files/import-keepass.txt
deleted file mode 100644
index 61ce7ad..0000000
--- a/test/files/import-keepass.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-"Group","Title","Username","Password","URL","Notes"
-"Racine","Bank","123456","ywcExJW8qmBVTSyi","http://bank.com/login","My little bank"
-"Racine/Cloud","GAFAM","wesh","superpassword","localhost.local",""
diff --git a/test/files/import-mpw_old.txt b/test/files/import-mpw_old.txt
deleted file mode 100644
index fd162aa..0000000
--- a/test/files/import-mpw_old.txt
+++ /dev/null
@@ -1,35 +0,0 @@
----
-1:
-  host: fric.com
-  user: 12345
-  group: Bank
-  password: secret
-  protocol: http
-  port:
-  otp_key: 
-  comment: Fric money money 
-  last_edit: 1487623641
-  created: 1485729356
-2:
-  host: server.com
-  user: sercret2
-  group:
-  password:
-  protocol:
-  port: 4222
-  otp_key: 
-  comment: My little server
-  last_edit: 1487623641
-  created: 1485729356
-3:
-  host: fric.com
-  user: username
-  group: Cloud
-  password:
-  protocol: ssh
-  port: 4333
-  otp_key: 
-  comment: bastion
-  last_edit: 1487623641
-  created: 1485729356
-
diff --git a/test/init.rb b/test/init.rb
deleted file mode 100644
index 08ebbb8..0000000
--- a/test/init.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-require 'fileutils'
-require 'gpgme'
-
-FileUtils.rm_rf("#{Dir.home}/.config/mpw")
-FileUtils.rm_rf("#{Dir.home}/.gnupg")
-
-param = ''
-param << '<GnupgKeyParms format="internal">' + "\n"
-param << "Key-Type: RSA\n"
-param << "Key-Length: 512\n"
-param << "Subkey-Type: ELG-E\n"
-param << "Subkey-Length: 512\n"
-param << "Name-Real: test\n"
-param << "Name-Comment: test\n"
-param << "Name-Email: test2@example.com\n"
-param << "Expire-Date: 0\n"
-param << "Passphrase: password\n"
-param << "</GnupgKeyParms>\n"
-
-ctx = GPGME::Ctx.new
-ctx.genkey(param, nil, nil)
diff --git a/test/test.sh b/test/test.sh
new file mode 100644
index 0000000..ab37885
--- /dev/null
+++ b/test/test.sh
@@ -0,0 +1 @@
+echo "test\ntest\n" | ruby ./bin/mpw config --init test@test.com
diff --git a/test/test2.rb b/test/test2.rb
new file mode 100644
index 0000000..17af2da
--- /dev/null
+++ b/test/test2.rb
@@ -0,0 +1,11 @@
+require 'open3'
+
+Open3.popen3("./bin/mpw config --init test@test.com") do |stdin, stdout, stderr, thread|
+	stdin.puts 'test'
+	stdin.puts 'test'
+end
+
+Open3.popen3("./bin/mpw list") do |stdin, stdout, stderr, thread|
+	stdin.puts 'test'
+	puts stdout
+end
diff --git a/test/test_cli.rb b/test/test_cli.rb
deleted file mode 100644
index 8bae4cc..0000000
--- a/test/test_cli.rb
+++ /dev/null
@@ -1,256 +0,0 @@
-require 'i18n'
-require 'test/unit'
-
-class TestConfig < Test::Unit::TestCase
-  def setup
-    if defined?(I18n.enforce_available_locales)
-      I18n.enforce_available_locales = true
-    end
-
-    I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
-    I18n.load_path = ["#{File.expand_path('../../i18n', __FILE__)}/en.yml"]
-    I18n.locale    = :en
-
-    @password = 'password'
-    @fixtures = YAML.load_file('./test/files/fixtures.yml')
-    @gpg_key  = 'test@example.com'
-  end
-
-  def test_00_init_config
-    output = %x(
-      echo "#{@password}\n#{@password}" | mpw config \
-      --init #{@gpg_key} \
-      2>/dev/null
-    )
-    assert_match(I18n.t('form.setup_config.valid'), output)
-    assert_match(I18n.t('form.setup_gpg_key.valid'), output)
-  end
-
-  def test_01_add_item
-    data = @fixtures['add']
-
-    output = %x(
-      echo #{@password} | mpw add \
-      --url #{data['url']} \
-      --user #{data['user']} \
-      --comment '#{data['comment']}' \
-      --group #{data['group']} \
-      --random \
-      2>/dev/null
-    )
-    assert_match(I18n.t('form.add_item.valid'), output)
-
-    output = %x(echo #{@password} | mpw list 2>/dev/null)
-    assert_match(%r{#{data['protocol']}://.+#{data['host']}.+:#{data['port']}}, output)
-    assert_match(data['user'], output)
-    assert_match(data['comment'], output)
-    assert_match(data['group'], output)
-  end
-
-  def test_02_search
-    data = @fixtures['add']
-
-    output = %x(echo #{@password} | mpw list --group #{data['group']} 2>/dev/null)
-    assert_match(%r{#{data['protocol']}://.+#{data['host']}.+:#{data['port']}}, output)
-
-    output = %x(echo #{@password} | mpw list --pattern #{data['host']} 2>/dev/null)
-    assert_match(%r{#{data['protocol']}://.+#{data['host']}.+:#{data['port']}}, output)
-
-    output = %x(echo #{@password} | mpw list --pattern #{data['comment']} 2>/dev/null)
-    assert_match(%r{#{data['protocol']}://.+#{data['host']}.+:#{data['port']}}, output)
-
-    output = %x(echo #{@password} | mpw list --group R1Pmfbp626TFpjlr 2>/dev/null)
-    assert_match(I18n.t('display.nothing'), output)
-
-    output = %x(echo #{@password} | mpw list --pattern h1IfnKqamaGM9oEX 2>/dev/null)
-    assert_match(I18n.t('display.nothing'), output)
-  end
-
-  def test_03_update_item
-    data = @fixtures['update']
-
-    output = %x(
-      echo #{@password} | mpw update \
-      -p #{@fixtures['add']['host']} \
-      --url #{data['url']} \
-      --user #{data['user']} \
-      --comment '#{data['comment']}' \
-      --new-group #{data['group']} \
-      2>/dev/null
-    )
-    assert_match(I18n.t('form.update_item.valid'), output)
-
-    output = %x(echo #{@password} | mpw list 2>/dev/null)
-    assert_match(%r{#{data['protocol']}://.+#{data['host']}.+:#{data['port']}}, output)
-    assert_match(data['user'], output)
-    assert_match(data['comment'], output)
-    assert_match(data['group'], output)
-  end
-
-  def test_04_delete_item
-    output = %x(
-      echo "#{@password}\ny" | mpw delete \
-      -p #{@fixtures['update']['host']} \
-      2>/dev/null
-    )
-    assert_match(I18n.t('form.delete_item.valid'), output)
-
-    output = %x(echo #{@password} | mpw list 2>/dev/null)
-    assert_match(I18n.t('display.nothing'), output)
-  end
-
-  def test_05_import_export
-    file_import = './test/files/fixtures-import.yml'
-    file_export = '/tmp/test-mpw.yml'
-
-    output = %x(echo #{@password} | mpw import --file #{file_import} 2>/dev/null)
-    assert_match(I18n.t('form.import.valid', file: file_import), output)
-
-    output = %x(echo #{@password} | mpw export --file #{file_export} 2>/dev/null)
-    assert_match(I18n.t('form.export.valid', file: file_export), output)
-    assert(File.exist?(file_export))
-    assert_equal(YAML.load_file(file_export).length, 2)
-
-    YAML.load_file(file_import).each_value do |import|
-      error = true
-
-      YAML.load_file(file_export).each_value do |export|
-        next if import['url'] != export['url']
-
-        %w[user group password protocol port otp_key comment].each do |key|
-          assert_equal(import[key].to_s, export[key].to_s)
-        end
-
-        error = false
-        break
-      end
-
-      assert(!error)
-    end
-  end
-
-  def test_06_copy
-    data = YAML.load_file('./test/files/fixtures-import.yml')[2]
-
-    output = %x(
-      echo "#{@password}\np\nq" | mpw copy \
-      --disable-clipboard \
-      -p #{data['host']} \
-      2>/dev/null
-    )
-    assert_match(data['password'], output)
-  end
-
-  def test_07_setup_wallet
-    gpg_key = 'test2@example.com'
-
-    output = %x(echo #{@password} | mpw wallet --add-gpg-key #{gpg_key} 2>/dev/null)
-    assert_match(I18n.t('form.add_key.valid'), output)
-
-    output = %x(echo #{@password} | mpw wallet --list-keys 2>/dev/null)
-    assert_match("| #{@gpg_key}", output)
-    assert_match("| #{gpg_key}", output)
-
-    output = %x(echo #{@password} | mpw wallet --delete-gpg-key #{gpg_key} 2>/dev/null)
-    assert_match(I18n.t('form.delete_key.valid'), output)
-
-    output = %x(echo #{@password} | mpw wallet --list-keys 2>/dev/null)
-    assert_match("| #{@gpg_key}", output)
-    assert_no_match(/\| #{gpg_key}/, output)
-
-    output = %x(mpw wallet)
-    assert_match('| default', output)
-
-    output = %x(mpw wallet --path '.')
-    assert_match(I18n.t('form.set_wallet_path.valid'), output)
-
-    output = %x(mpw config)
-    assert_match(%r{path_wallet_default.+\| #{Dir.pwd}/default.mpw}, output)
-    assert(File.exist?("#{Dir.pwd}/default.mpw"))
-
-    output = %x(mpw wallet)
-    assert_match('default', output)
-
-    output = %x(mpw wallet --default-path)
-    assert_match(I18n.t('form.set_wallet_path.valid'), output)
-
-    output = %x(mpw config)
-    assert_no_match(/path_wallet_default/, output)
-  end
-
-  def test_08_setup_config
-    gpg_key    = 'test2@example.com'
-    gpg_exe    = '/usr/bin/gpg2'
-    wallet_dir = '/tmp'
-    length     = 24
-    wallet     = 'work'
-
-    output = %x(
-      mpw config \
-      --gpg-exe #{gpg_exe} \
-      --key #{gpg_key} \
-      --enable-pinmode \
-      --disable-alpha \
-      --disable-special-chars \
-      --disable-numeric \
-      --length #{length} \
-      --wallet-dir #{wallet_dir} \
-      --default-wallet #{wallet}
-    )
-    assert_match(I18n.t('form.set_config.valid'), output)
-
-    output = %x(mpw config)
-    assert_match(/gpg_key.+\| #{gpg_key}/, output)
-    assert_match(/gpg_exe.+\| #{gpg_exe}/, output)
-    assert_match(/pinmode.+\| true/, output)
-    assert_match(/default_wallet.+\| #{wallet}/, output)
-    assert_match(/wallet_dir.+\| #{wallet_dir}/, output)
-    assert_match(/password_length.+\| #{length}/, output)
-    %w[numeric alpha special].each do |k|
-      assert_match(/password_#{k}.+\| false/, output)
-    end
-
-    output = %x(
-      mpw config \
-      --gpg-exe '' \
-      --key #{@gpg_key} \
-      --alpha \
-      --special-chars \
-      --numeric \
-      --disable-pinmode
-    )
-    assert_match(I18n.t('form.set_config.valid'), output)
-
-    output = %x(mpw config)
-    assert_match(/gpg_key.+\| #{@gpg_key}/, output)
-    assert_match(/pinmode.+\| false/, output)
-    %w[numeric alpha special].each do |k|
-      assert_match(/password_#{k}.+\| true/, output)
-    end
-  end
-
-  def test_09_generate_password
-    length = 24
-
-    output = %x(
-      mpw genpwd \
-      --length #{length} \
-      --alpha
-    )
-    assert_match(/[a-zA-Z]{#{length}}/, output)
-
-    output = %x(
-      mpw genpwd \
-      --length #{length} \
-      --numeric
-    )
-    assert_match(/[0-9]{#{length}}/, output)
-
-    output = %x(
-      mpw genpwd \
-      --length #{length} \
-      --special-chars
-    )
-    assert_no_match(/[a-zA-Z0-9]/, output)
-  end
-end
diff --git a/test/test_config.rb b/test/test_config.rb
index 2b88b54..3de438c 100644
--- a/test/test_config.rb
+++ b/test/test_config.rb
@@ -1,79 +1,40 @@
+#!/usr/bin/ruby
+
 require 'mpw/config'
 require 'test/unit'
 require 'locale'
 require 'i18n'
 
 class TestConfig < Test::Unit::TestCase
-  def setup
-    lang = Locale::Tag.parse(ENV['LANG']).to_simple.to_s[0..1]
+	def setup
+		lang = Locale::Tag.parse(ENV['LANG']).to_simple.to_s[0..1]
+		
+		if defined?(I18n.enforce_available_locales)
+			I18n.enforce_available_locales = true
+		end
+		
+		I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
+		I18n.load_path      = Dir["#{File.expand_path('../../i18n', __FILE__)}/*.yml"]
+		I18n.default_locale = :en
+		I18n.locale         = lang.to_sym
+	end
 
-    if defined?(I18n.enforce_available_locales)
-      I18n.enforce_available_locales = true
-    end
+	def test_00_config
+		data = { key: 'test@example.com',
+		         lang: 'en',
+		         wallet_dir: '/tmp/test',
+		         gpg_exe: '',
+		       }
 
-    I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
-    I18n.load_path      = Dir["#{File.expand_path('../../i18n', __FILE__)}/*.yml"]
-    I18n.default_locale = :en
-    I18n.locale         = lang.to_sym
-  end
+		@config = MPW::Config.new
+		@config.setup(data[:key], data[:lang], data[:wallet_dir], data[:gpg_exe])
+		@config.load_config
 
-  def test_00_config
-    data = {
-      gpg_key: 'test@example.com',
-      lang: 'en',
-      wallet_dir: '/tmp/test',
-      gpg_exe: ''
-    }
+		data.each do |k,v|
+			assert_equal(v, @config.send(k))
+		end
 
-    @config = MPW::Config.new
-    @config.setup(data)
-    @config.load_config
-
-    data.each do |k, v|
-      assert_equal(v, @config.send(k))
-    end
-
-    @config.setup_gpg_key('password', 'test@example.com', 2048)
-    assert(@config.check_gpg_key?)
-  end
-
-  def test_01_password
-    data = {
-      pwd_alpha: false,
-      pwd_numeric: false,
-      pwd_special: true,
-      pwd_length: 32
-    }
-
-    @config = MPW::Config.new
-    @config.load_config
-
-    assert_equal(@config.password[:length], 16)
-    assert(@config.password[:alpha])
-    assert(@config.password[:numeric])
-    assert(!@config.password[:special])
-
-    @config.setup(data)
-    @config.load_config
-
-    assert_equal(@config.password[:length], data[:pwd_length])
-    assert(!@config.password[:alpha])
-    assert(!@config.password[:numeric])
-    assert(@config.password[:special])
-  end
-
-  def test_02_wallet_paths
-    new_path = '/tmp/mpw-test'
-
-    @config = MPW::Config.new
-    @config.load_config
-
-    assert(!@config.wallet_paths['default'])
-
-    @config.set_wallet_path(new_path, 'default')
-    assert_equal(@config.wallet_paths['default'], new_path)
-
-    @config.set_wallet_path('default', 'default')
-    assert(!@config.wallet_paths['default'])
-  end
+		@config.setup_gpg_key('password', 'test@example.com', 2048)
+		assert(@config.check_gpg_key?)
+	end
 end
diff --git a/test/test_import.rb b/test/test_import.rb
deleted file mode 100644
index f1a8727..0000000
--- a/test/test_import.rb
+++ /dev/null
@@ -1,79 +0,0 @@
-require 'i18n'
-require 'test/unit'
-
-class TestImport < Test::Unit::TestCase
-  def setup
-    if defined?(I18n.enforce_available_locales)
-      I18n.enforce_available_locales = true
-    end
-
-    I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
-    I18n.load_path = ["#{File.expand_path('../../i18n', __FILE__)}/en.yml"]
-    I18n.locale    = :en
-
-    @password = 'password'
-  end
-
-  def test_00_import_mpw_old
-    file = './test/files/import-mpw_old.txt'
-    format = 'mpw_old'
-
-    output = %x(
-        mpw import \
-        --file #{file} \
-        --format #{format} \
-        --wallet #{format}
-      )
-    assert_match(I18n.t('form.import.valid'), output)
-
-    output = %x(echo #{@password} | mpw list --group Bank --wallet #{format})
-    assert_match(%r{http://.*fric\.com.*12345.*Fric money money}, output)
-
-    output = %x(echo #{@password} | mpw list --group Cloud --wallet #{format})
-    assert_match(%r{ssh://.*fric\.com.*:4333.*username.*bastion}, output)
-
-    output = %x(echo #{@password} | mpw list --wallet #{format})
-    assert_match(/server\.com.*My little server/, output)
-  end
-
-  def test_01_import_gorilla
-    file = './test/files/import-gorilla.txt'
-    format = 'gorilla'
-
-    output = %x(
-        mpw import \
-        --file #{file} \
-        --format #{format} \
-        --wallet #{format}
-      )
-    assert_match(I18n.t('form.import.valid'), output)
-
-    output = %x(echo #{@password} | mpw list --group Bank --wallet #{format})
-    assert_match(%r{http://.*fric\.com.*12345.*Fric money money}, output)
-
-    output = %x(echo #{@password} | mpw list --group Cloud --wallet #{format})
-    assert_match(%r{ssh://.*fric\.com.*:4333.*username.*bastion}, output)
-
-    output = %x(echo #{@password} | mpw list --wallet #{format})
-    assert_match(/server\.com.*My little server/, output)
-  end
-
-  def test_02_import_keepass
-    file = './test/files/import-keepass.txt'
-    format = 'keepass'
-
-    output = %x(
-        mpw import \
-        --file #{file} \
-        --format #{format} \
-        --wallet #{format}
-      )
-    assert_match(I18n.t('form.import.valid'), output)
-
-    output = %x(echo #{@password} | mpw list --group 'Racine/Cloud' --wallet #{format})
-    assert_match(/localhost\.local.*wesh.*GAFAM/, output)
-
-    output = %x(echo #{@password} | mpw list --wallet #{format})
-    assert_match(%r{http://.*bank\.com.*123456.*Bank My little bank}, output)
-  end
-end
diff --git a/test/test_item.rb b/test/test_item.rb
index 97503c0..b9f5961 100644
--- a/test/test_item.rb
+++ b/test/test_item.rb
@@ -1,165 +1,172 @@
+#!/usr/bin/ruby
+ 
 require 'mpw/item'
 require 'test/unit'
 require 'yaml'
-
+ 
 class TestItem < Test::Unit::TestCase
-  def setup
-    if defined?(I18n.enforce_available_locales)
-      I18n.enforce_available_locales = false
-    end
+	def setup
+		@fixture_file = 'test/files/fixtures.yml'
+		@fixtures = YAML.load_file(@fixture_file)
+		
+		if defined?(I18n.enforce_available_locales)
+			I18n.enforce_available_locales = false
+		end
 
-    I18n.load_path      = Dir['./i18n/cli/*.yml']
-    I18n.default_locale = :en
+		I18n.load_path      = Dir['./i18n/cli/*.yml']
+		I18n.default_locale = :en
 
-    @fixtures = YAML.load_file('./test/files/fixtures.yml')
-  end
 
-  def test_00_add_without_name
-    assert_raise(RuntimeError) { MPW::Item.new }
-  end
+		puts
+	end
 
-  def test_01_add
-    data = {
-      group:    @fixtures['add']['group'],
-      user:     @fixtures['add']['user'],
-      url:      @fixtures['add']['url'],
-      comment:  @fixtures['add']['comment']
-    }
+	def test_00_add_without_name
+		assert_raise(RuntimeError){MPW::Item.new}
+	end
 
-    item = MPW::Item.new(data)
+	def test_01_add_new
+		data = { group:    @fixtures['add_new']['group'],
+		         host:     @fixtures['add_new']['host'],
+		         protocol: @fixtures['add_new']['protocol'],
+		         user:     @fixtures['add_new']['user'],
+		         port:     @fixtures['add_new']['port'],
+		         comment:  @fixtures['add_new']['comment'],
+		       }
+		
+		item = MPW::Item.new(data)
 
-    assert(!item.nil?)
-    assert(!item.empty?)
+		assert(!item.nil?)
+		assert(!item.empty?)
 
-    assert_equal(@fixtures['add']['url'],       item.url)
-    assert_equal(@fixtures['add']['group'],     item.group)
-    assert_equal(@fixtures['add']['host'],      item.host)
-    assert_equal(@fixtures['add']['protocol'],  item.protocol)
-    assert_equal(@fixtures['add']['user'],      item.user)
-    assert_equal(@fixtures['add']['port'].to_i, item.port)
-    assert_equal(@fixtures['add']['comment'],   item.comment)
-  end
+		assert_equal(@fixtures['add_new']['group'],     item.group)
+		assert_equal(@fixtures['add_new']['host'],      item.host)
+		assert_equal(@fixtures['add_new']['protocol'],  item.protocol)
+		assert_equal(@fixtures['add_new']['user'],      item.user)
+		assert_equal(@fixtures['add_new']['port'].to_i, item.port)
+		assert_equal(@fixtures['add_new']['comment'],   item.comment)
+	end
 
-  def test_02_import
-    data = {
-      id:       @fixtures['import']['id'],
-      group:    @fixtures['import']['group'],
-      user:     @fixtures['import']['user'],
-      url:      @fixtures['import']['url'],
-      comment:  @fixtures['import']['comment'],
-      created:  @fixtures['import']['created']
-    }
+	def test_02_add_existing
+		data = { id:       @fixtures['add_existing']['id'],
+		         group:    @fixtures['add_existing']['group'],
+		         host:     @fixtures['add_existing']['host'],
+		         protocol: @fixtures['add_existing']['protocol'],
+		         user:     @fixtures['add_existing']['user'],
+		         port:     @fixtures['add_existing']['port'],
+		         comment:  @fixtures['add_existing']['comment'],
+		         created:  @fixtures['add_existing']['created'],
+		       }
 
-    item = MPW::Item.new(data)
+		item = MPW::Item.new(data)
 
-    assert(!item.nil?)
-    assert(!item.empty?)
+		assert(!item.nil?)
+		assert(!item.empty?)
 
-    assert_equal(@fixtures['import']['id'],        item.id)
-    assert_equal(@fixtures['import']['url'],       item.url)
-    assert_equal(@fixtures['import']['group'],     item.group)
-    assert_equal(@fixtures['import']['host'],      item.host)
-    assert_equal(@fixtures['import']['protocol'],  item.protocol)
-    assert_equal(@fixtures['import']['user'],      item.user)
-    assert_equal(@fixtures['import']['port'].to_i, item.port)
-    assert_equal(@fixtures['import']['comment'],   item.comment)
-    assert_equal(@fixtures['import']['created'],   item.created)
-  end
+		assert_equal(@fixtures['add_existing']['id'],        item.id)
+		assert_equal(@fixtures['add_existing']['group'],     item.group)
+		assert_equal(@fixtures['add_existing']['host'],      item.host)
+		assert_equal(@fixtures['add_existing']['protocol'],  item.protocol)
+		assert_equal(@fixtures['add_existing']['user'],      item.user)
+		assert_equal(@fixtures['add_existing']['port'].to_i, item.port)
+		assert_equal(@fixtures['add_existing']['comment'],   item.comment)
+		assert_equal(@fixtures['add_existing']['created'],   item.created)
+	end
 
-  def test_03_update
-    data = {
-      group:    @fixtures['add']['group'],
-      user:     @fixtures['add']['user'],
-      url:      @fixtures['add']['url'],
-      comment:  @fixtures['add']['comment']
-    }
+	def test_03_update
+		data = { group:    @fixtures['add_new']['group'],
+		         host:     @fixtures['add_new']['host'],
+		         protocol: @fixtures['add_new']['protocol'],
+		         user:     @fixtures['add_new']['user'],
+		         port:     @fixtures['add_new']['port'],
+		         comment:  @fixtures['add_new']['comment'],
+		       }
+		
+		item = MPW::Item.new(data)
 
-    item = MPW::Item.new(data)
+		assert(!item.nil?)
+		assert(!item.empty?)
 
-    assert(!item.nil?)
-    assert(!item.empty?)
+		created   = item.created
+		last_edit = item.last_edit
 
-    created   = item.created
-    last_edit = item.last_edit
+		data = { group:    @fixtures['update']['group'],
+		         host:     @fixtures['update']['host'],
+		         protocol: @fixtures['update']['protocol'],
+		         user:     @fixtures['update']['user'],
+		         port:     @fixtures['update']['port'],
+		         comment:  @fixtures['update']['comment'],
+		       }
+		
+		sleep(1)
+		assert(item.update(data))
 
-    data = {
-      group:    @fixtures['update']['group'],
-      user:     @fixtures['update']['user'],
-      url:      @fixtures['update']['url'],
-      comment:  @fixtures['update']['comment']
-    }
+		assert(!item.empty?)
 
-    sleep(1)
-    assert(item.update(data))
+		assert_equal(@fixtures['update']['group'],     item.group)
+		assert_equal(@fixtures['update']['host'],      item.host)
+		assert_equal(@fixtures['update']['protocol'],  item.protocol)
+		assert_equal(@fixtures['update']['user'],      item.user)
+		assert_equal(@fixtures['update']['port'].to_i, item.port)
+		assert_equal(@fixtures['update']['comment'],   item.comment)
 
-    assert(!item.empty?)
+		assert_equal(created, item.created)
+		assert_not_equal(last_edit, item.last_edit)
+	end
 
-    assert_equal(@fixtures['update']['url'],       item.url)
-    assert_equal(@fixtures['update']['group'],     item.group)
-    assert_equal(@fixtures['update']['host'],      item.host)
-    assert_equal(@fixtures['update']['protocol'],  item.protocol)
-    assert_equal(@fixtures['update']['user'],      item.user)
-    assert_equal(@fixtures['update']['port'].to_i, item.port)
-    assert_equal(@fixtures['update']['comment'],   item.comment)
+	def test_05_update_one_element
+		data = { group:    @fixtures['add_new']['group'],
+		         host:     @fixtures['add_new']['host'],
+		         protocol: @fixtures['add_new']['protocol'],
+		         user:     @fixtures['add_new']['user'],
+		         port:     @fixtures['add_new']['port'],
+		         comment:  @fixtures['add_new']['comment'],
+		       }
+		
+		item = MPW::Item.new(data)
 
-    assert_equal(created, item.created)
-    assert_not_equal(last_edit, item.last_edit)
-  end
+		assert(!item.nil?)
+		assert(!item.empty?)
 
-  def test_05_update_one_element
-    data = {
-      group:    @fixtures['add']['group'],
-      user:     @fixtures['add']['user'],
-      url:      @fixtures['add']['url'],
-      comment:  @fixtures['add']['comment']
-    }
+		last_edit = item.last_edit
 
-    item = MPW::Item.new(data)
+		sleep(1)
+		assert(item.update({comment: @fixtures['update']['comment']}))
 
-    assert(!item.nil?)
-    assert(!item.empty?)
+		assert_equal(@fixtures['add_new']['group'],     item.group)
+		assert_equal(@fixtures['add_new']['host'],      item.host)
+		assert_equal(@fixtures['add_new']['protocol'],  item.protocol)
+		assert_equal(@fixtures['add_new']['user'],      item.user)
+		assert_equal(@fixtures['add_new']['port'].to_i, item.port)
+		assert_equal(@fixtures['update']['comment'],    item.comment)
+	
+		assert_not_equal(last_edit, item.last_edit)
+	end
 
-    last_edit = item.last_edit
+	def test_05_delete
+		data = { group:    @fixtures['add_new']['group'],
+		         host:     @fixtures['add_new']['host'],
+		         protocol: @fixtures['add_new']['protocol'],
+		         user:     @fixtures['add_new']['user'],
+		         port:     @fixtures['add_new']['port'],
+		         comment:  @fixtures['add_new']['comment'],
+		       }
+		
+		item = MPW::Item.new(data)
 
-    sleep(1)
-    item.update(comment: @fixtures['update']['comment'])
+		assert(!item.nil?)
+		assert(!item.empty?)
 
-    assert_equal(@fixtures['add']['url'],        item.url)
-    assert_equal(@fixtures['add']['group'],      item.group)
-    assert_equal(@fixtures['add']['host'],       item.host)
-    assert_equal(@fixtures['add']['protocol'],   item.protocol)
-    assert_equal(@fixtures['add']['user'],       item.user)
-    assert_equal(@fixtures['add']['port'].to_i,  item.port)
-    assert_equal(@fixtures['update']['comment'], item.comment)
+		item.delete
+		assert(!item.nil?)
+		assert(item.empty?)
 
-    assert_not_equal(last_edit, item.last_edit)
-  end
-
-  def test_05_delete
-    data = {
-      group:    @fixtures['add']['group'],
-      user:     @fixtures['add']['user'],
-      url:      @fixtures['add']['url'],
-      comment:  @fixtures['add']['comment']
-    }
-
-    item = MPW::Item.new(data)
-
-    assert(!item.nil?)
-    assert(!item.empty?)
-
-    item.delete
-    assert(!item.nil?)
-    assert(item.empty?)
-
-    assert_equal(nil, item.id)
-    assert_equal(nil, item.url)
-    assert_equal(nil, item.group)
-    assert_equal(nil, item.host)
-    assert_equal(nil, item.protocol)
-    assert_equal(nil, item.user)
-    assert_equal(nil, item.port)
-    assert_equal(nil, item.comment)
-    assert_equal(nil, item.created)
-  end
-end
+		assert_equal(nil, item.id)
+		assert_equal(nil, item.group)
+		assert_equal(nil, item.host)
+		assert_equal(nil, item.protocol)
+		assert_equal(nil, item.user)
+		assert_equal(nil, item.port)
+		assert_equal(nil, item.comment)
+		assert_equal(nil, item.created)
+	end
+end 
diff --git a/test/test_mpw.rb b/test/test_mpw.rb
index 8fc544e..0c6a25b 100644
--- a/test/test_mpw.rb
+++ b/test/test_mpw.rb
@@ -1,130 +1,121 @@
+#!/usr/bin/ruby
+ 
 require 'mpw/mpw'
 require 'mpw/item'
 require 'test/unit'
 require 'yaml'
 require 'csv'
-
+ 
 class TestMPW < Test::Unit::TestCase
-  def setup
-    wallet_file = 'default.gpg'
-    key         = 'test@example.com'
-    password    = 'password'
+	def setup
+		fixture_file = './test/files/fixtures.yml'
 
-    if defined?(I18n.enforce_available_locales)
-      I18n.enforce_available_locales = false
-    end
+		wallet_file = 'default.gpg'
+		key         = 'test@example.com'
+		password    = 'password'
 
-    @mpw      = MPW::MPW.new(key, wallet_file, password)
-    @fixtures = YAML.load_file('./test/files/fixtures.yml')
-  end
+		if defined?(I18n.enforce_available_locales)
+			I18n.enforce_available_locales = false
+		end
 
-  def test_00_decrypt_empty_file
-    @mpw.read_data
-    assert_equal(0, @mpw.list.length)
-  end
+		@mpw      = MPW::MPW.new(key, wallet_file, password)
+		@fixtures = YAML.load_file(fixture_file)
+	end
+ 
+	def test_00_decrypt_empty_file
+		@mpw.read_data
+		assert_equal(0, @mpw.list.length)
+	end
 
-  def test_01_encrypt_empty_file
-    @mpw.read_data
-    @mpw.write_data
-  end
+	def test_01_encrypt_empty_file
+		@mpw.read_data
+		@mpw.write_data
+	end
 
-  def test_02_add_item
-    data = {
-      group:    @fixtures['add']['group'],
-      user:     @fixtures['add']['user'],
-      url:      @fixtures['add']['url'],
-      comment:  @fixtures['add']['comment']
-    }
+	def test_02_add_item
+		data = { group:    @fixtures['add_new']['group'],
+		         host:     @fixtures['add_new']['host'],
+		         protocol: @fixtures['add_new']['protocol'],
+		         user:     @fixtures['add_new']['user'],
+		         port:     @fixtures['add_new']['port'],
+		         comment:  @fixtures['add_new']['comment'],
+		       }
+		
+		item = MPW::Item.new(data)
 
-    item = MPW::Item.new(data)
+		assert(!item.nil?)
+		assert(!item.empty?)
 
-    assert(!item.nil?)
-    assert(!item.empty?)
+		@mpw.read_data
+		@mpw.add(item)
+		@mpw.set_password(item.id, @fixtures['add_new']['password'])
 
-    @mpw.read_data
-    @mpw.add(item)
-    @mpw.set_password(item.id, @fixtures['add']['password'])
+		assert_equal(1, @mpw.list.length)
 
-    assert_equal(1, @mpw.list.length)
+		item = @mpw.list[0]
+		@fixtures['add_new'].each do |k,v|
+			if k == 'password'
+				assert_equal(v, @mpw.get_password(item.id))
+			else
+				assert_equal(v, item.send(k).to_s)
+			end
+		end
 
-    item = @mpw.list[0]
-    @fixtures['add'].each do |k, v|
-      if k == 'password'
-        assert_equal(v, @mpw.get_password(item.id))
-      else
-        assert_equal(v, item.send(k).to_s)
-      end
-    end
+		@mpw.write_data
+	end
 
-    @mpw.write_data
-  end
+	def test_03_decrypt_file
+		@mpw.read_data
+		assert_equal(1, @mpw.list.length)
 
-  def test_03_decrypt_file
-    @mpw.read_data
-    assert_equal(1, @mpw.list.length)
+		item = @mpw.list[0]
+		@fixtures['add_new'].each do |k,v|
+			if k == 'password'
+				assert_equal(v, @mpw.get_password(item.id))
+			else
+				assert_equal(v, item.send(k).to_s)
+			end
+		end
+	end
 
-    item = @mpw.list[0]
-    @fixtures['add'].each do |k, v|
-      if k == 'password'
-        assert_equal(v, @mpw.get_password(item.id))
-      else
-        assert_equal(v, item.send(k).to_s)
-      end
-    end
-  end
+	def test_04_delete_item
+		@mpw.read_data
 
-  def test_04_delete_item
-    @mpw.read_data
-    assert_equal(1, @mpw.list.length)
+		assert_equal(1, @mpw.list.length)
 
-    @mpw.list.each(&:delete)
-    assert_equal(0, @mpw.list.length)
+		@mpw.list.each do |item|
+			item.delete
+		end
 
-    @mpw.write_data
-  end
+		assert_equal(0, @mpw.list.length)
 
-  def test_05_search
-    @mpw.read_data
+		@mpw.write_data
+	end
 
-    @fixtures.each_value do |v|
-      data = {
-        group:    v['group'],
-        user:     v['user'],
-        url:      v['url'],
-        comment:  v['comment']
-      }
+	def test_05_search
+		@mpw.read_data
 
-      item = MPW::Item.new(data)
+		@fixtures.each_value do |v|
+			data = { group:    v['group'],
+			         host:     v['host'],
+			         protocol: v['protocol'],
+			         user:     v['user'],
+			         port:     v['port'],
+			         comment:  v['comment'],
+			       }
+			
+			item = MPW::Item.new(data)
+	
+			assert(!item.nil?)
+			assert(!item.empty?)
+	
+			@mpw.add(item)
+			@mpw.set_password(item.id, v['password'])
+		end
 
-      assert(!item.nil?)
-      assert(!item.empty?)
-
-      @mpw.add(item)
-      @mpw.set_password(item.id, v['password'])
-    end
-
-    assert_equal(3, @mpw.list.length)
-    assert_equal(1, @mpw.list(group:   @fixtures['add']['group']).length)
-    assert_equal(1, @mpw.list(pattern: 'gogole').length)
-    assert_equal(2, @mpw.list(pattern: 'example[2\.]').length)
-  end
-
-  def test_06_add_gpg_key
-    @mpw.read_data
-
-    @mpw.add_key('test2@example.com')
-    assert_equal(2, @mpw.list_keys.length)
-
-    @mpw.write_data
-  end
-
-  def test_07_delete_gpg_key
-    @mpw.read_data
-    assert_equal(2, @mpw.list_keys.length)
-
-    @mpw.delete_key('test2@example.com')
-    assert_equal(1, @mpw.list_keys.length)
-
-    @mpw.write_data
-  end
+		assert_equal(3, @mpw.list.length)
+		assert_equal(1, @mpw.list(group:    @fixtures['add_new']['group']).length)
+		assert_equal(1, @mpw.list(pattern:  'existing').length)
+		assert_equal(2, @mpw.list(pattern:  'host_[eu]').length)
+	end
 end
diff --git a/test/test_translate.rb b/test/test_translate.rb
deleted file mode 100644
index d7cbceb..0000000
--- a/test/test_translate.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-require 'yaml'
-require 'test/unit'
-
-class TestTranslate < Test::Unit::TestCase
-  def test_00_check_translate
-    missing = 0
-
-    Dir.glob('i18n/*.yml').each do |yaml|
-      lang      = File.basename(yaml, '.yml')
-      translate = YAML.load_file(yaml)
-
-      %x(grep -r -o "I18n.t('.*)" bin/ lib/ | cut -d"'" -f2).each_line do |line|
-        begin
-          t = translate[lang]
-          line.strip.split('.').each do |v|
-            t = t[v]
-          end
-
-          assert(!t.to_s.empty?)
-        rescue
-          puts "#{lang}.#{line}"
-          missing = 1
-        end
-      end
-    end
-
-    assert_equal(0, missing)
-  end
-end
diff --git a/test/tests.rb b/test/tests.rb
new file mode 100644
index 0000000..b85efc0
--- /dev/null
+++ b/test/tests.rb
@@ -0,0 +1,5 @@
+#!/usr/bin/ruby
+ 
+require_relative 'test_config.rb'
+require_relative 'test_item.rb'
+require_relative 'test_mpw.rb'