diff --git a/app/cli.js b/app/cli.js
index d581b6fa01d3c26bd060b577657b67b407f3dfa8..c3260ec82c7f07f881a03bfd370a1d25d50c4382 100644
--- a/app/cli.js
+++ b/app/cli.js
@@ -5,7 +5,7 @@ const logger = require('../app/lib/logger')('cli');
 const async = require('async');
 const Q = require('q');
 const _ = require('underscore');
-const program = require('commander');
+const Command = require('commander').Command;
 const contacter = require('../app/lib/contacter');
 const directory = require('../app/lib/system/directory');
 const wizard = require('../app/lib/wizard');
@@ -18,306 +18,516 @@ const Peer = require('../app/lib/entity/peer');
 const Block = require('../app/lib/entity/block');
 const constants = require('../app/lib/constants');
 
-let currentCommand = Promise.resolve(true);
+module.exports = () => {
 
-let onResolve, onReject, onService, closeCommand = () => Promise.resolve(true);
+  const ERASE_IF_ALREADY_RECORDED = true;
+  const NO_LOGS = true;
 
-module.exports = (programArgs) => {
+  const options = [];
+  const commands = [];
 
-  currentCommand = new Promise((resolve, reject) => {
-    onResolve = resolve;
-    onReject = reject;
-  });
-  
   return {
 
-    // Some external event can trigger the program closing function
-    closeCommand: () => closeCommand(),
+    addOption: (optFormat, optDesc, optParser) => options.push({ optFormat, optDesc, optParser }),
+
+    addCommand: (command, executionCallback) => commands.push({ command, executionCallback }),
 
     // To execute the provided command
-    execute: (onServiceCallback) => co(function*() {
+    execute: (programArgs, onServiceCallback) => co(function*() {
 
-      onService = onServiceCallback;
-      program.parse(programArgs);
+      const program = new Command();
 
-      if (programArgs.length <= 2) {
-        onReject('No command given.');
-      }
+      let onResolve, onReject = () => Promise.reject(Error("Uninitilized rejection throw")), onService, closeCommand = () => Promise.resolve(true);
+      const currentCommand = new Promise((resolve, reject) => {
+        onResolve = resolve;
+        onReject = reject;
+      });
 
-      const res = yield currentCommand;
-      if (closeCommand) {
-        yield closeCommand();
+      program
+        .version(pjson.version)
+        .usage('<command> [options]')
+
+        .option('--home <path>', 'Path to Duniter HOME (defaults to "$HOME/.config/duniter").')
+        .option('-d, --mdb <name>', 'Database name (defaults to "duniter_default").')
+
+        .option('--autoconf', 'With `config` and `init` commands, will guess the best network and key options witout asking for confirmation')
+        .option('--ipv4 <address>', 'IPv4 interface to listen for requests')
+        .option('--ipv6 <address>', 'IPv6 interface to listen for requests')
+        .option('--remoteh <host>', 'Remote interface others may use to contact this node')
+        .option('--remote4 <host>', 'Remote interface for IPv4 access')
+        .option('--remote6 <host>', 'Remote interface for IPv6 access')
+        .option('-p, --port <port>', 'Port to listen for requests', parseInt)
+        .option('--remotep <port>', 'Remote port others may use to contact this node')
+        .option('--upnp', 'Use UPnP to open remote port')
+        .option('--noupnp', 'Do not use UPnP to open remote port')
+        .option('--addep <endpoint>', 'With `config` command, add given endpoint to the list of endpoints of this node')
+        .option('--remep <endpoint>', 'With `config` command, remove given endpoint to the list of endpoints of this node')
+
+        .option('--salt <salt>', 'Key salt to generate this key\'s secret key')
+        .option('--passwd <password>', 'Password to generate this key\'s secret key')
+        .option('--participate <Y|N>', 'Participate to writing the blockchain')
+        .option('--cpu <percent>', 'Percent of CPU usage for proof-of-work computation', parsePercent)
+
+        .option('-c, --currency <name>', 'Name of the currency managed by this node.')
+        .option('--sigPeriod <timestamp>', 'Minimum delay between 2 certifications of a same issuer, in seconds.')
+        .option('--sigStock <count>', 'Maximum quantity of valid certifications per member.')
+        .option('--sigWindow <duration>', 'Maximum age of a non-written certification.')
+        .option('--idtyWindow <duration>', 'Maximum age of a non-written certification.')
+        .option('--sigValidity <timestamp>', 'Validity duration of a certification, in seconds.')
+        .option('--msValidity <timestamp>', 'Validity duration of a memberships, in seconds.')
+        .option('--sigQty <number>', 'Minimum number of required certifications to be a member/stay as a member')
+        .option('--medtblocks <number>', 'medianTimeBlocks parameter of UCP')
+        .option('--avgGenTime <number>', 'avgGenTime parameter of UCP')
+        .option('--dtdiffeval <number>', 'dtDiffEval parameter of UCP')
+        .option('--powZeroMin <number>', 'Minimum number of leading zeros for a proof-of-work')
+        .option('--powPeriod <number>', 'Number of blocks to wait to decrease proof-of-work difficulty by one')
+        .option('--powDelay <number>', 'Number of seconds to wait before starting the computation of next block')
+        .option('--growth <number>', 'Universal Dividend %growth. Aka. \'c\' parameter in RTM', parsePercent)
+        .option('--ud0 <number>', 'Universal Dividend initial value')
+        .option('--dt <number>', 'Number of seconds between two UD')
+        .option('--rootoffset <number>', 'Allow to give a time offset for first block (offset in the past)')
+        .option('--show', 'With gen-next or gen-root commands, displays the generated block')
+
+        .option('--nointeractive', 'Disable interactive sync UI')
+        .option('--nocautious', 'Do not check blocks validity during sync')
+        .option('--cautious', 'Check blocks validity during sync (overrides --nocautious option)')
+        .option('--nopeers', 'Do not retrieve peers during sync')
+        .option('--nostdout', 'Disable stdout printing for `export-bc` command')
+        .option('--noshuffle', 'Disable peers shuffling for `sync` command')
+
+        .option('--timeout <milliseconds>', 'Timeout to use when contacting peers', parseInt)
+        .option('--httplogs', 'Enable HTTP logs')
+        .option('--nohttplogs', 'Disable HTTP logs')
+        .option('--isolate', 'Avoid the node to send peering or status informations to the network')
+        .option('--check', 'With gen-next: just check validity of generated block')
+        .option('--forksize <size>', 'Maximum size of fork window', parseInt)
+        .option('--memory', 'Memory mode')
+      ;
+
+      for (const opt of options) {
+        program
+          .option(opt.optFormat, opt.optDesc, opt.optParser);
       }
-      return res;
-    })
-  };
-};
 
-function subCommand(promiseFunc) {
-  return function() {
-    let args = Array.prototype.slice.call(arguments, 0);
-    return co(function*() {
-      try {
-        let result = yield promiseFunc.apply(null, args);
-        onResolve(result);
-      } catch (e) {
-        if (e && e.uerr) {
-          onReject(e.uerr.message);
-        } else {
-          onReject(e);
-        }
+      for (const cmd of commands) {
+        program
+          .command(cmd.command.name)
+          .description(cmd.command.desc)
+          .action((...args) => co(function*() {
+            try {
+              const res = yield cmd.executionCallback.apply(null, [program].concat(args));
+              onResolve(res);
+            } catch (e) {
+              onReject(e);
+            }
+          }));
       }
-    })
-  };
-}
 
-const ERASE_IF_ALREADY_RECORDED = true;
-const NO_LOGS = true;
-
-program
-  .version(pjson.version)
-  .usage('<command> [options]')
-
-  .option('--home <path>', 'Path to Duniter HOME (defaults to "$HOME/.config/duniter").')
-  .option('-d, --mdb <name>', 'Database name (defaults to "duniter_default").')
-
-  .option('--autoconf', 'With `config` and `init` commands, will guess the best network and key options witout asking for confirmation')
-  .option('--ipv4 <address>', 'IPv4 interface to listen for requests')
-  .option('--ipv6 <address>', 'IPv6 interface to listen for requests')
-  .option('--remoteh <host>', 'Remote interface others may use to contact this node')
-  .option('--remote4 <host>', 'Remote interface for IPv4 access')
-  .option('--remote6 <host>', 'Remote interface for IPv6 access')
-  .option('-p, --port <port>', 'Port to listen for requests', parseInt)
-  .option('--remotep <port>', 'Remote port others may use to contact this node')
-  .option('--upnp', 'Use UPnP to open remote port')
-  .option('--noupnp', 'Do not use UPnP to open remote port')
-  .option('--addep <endpoint>', 'With `config` command, add given endpoint to the list of endpoints of this node')
-  .option('--remep <endpoint>', 'With `config` command, remove given endpoint to the list of endpoints of this node')
-
-  .option('--salt <salt>', 'Key salt to generate this key\'s secret key')
-  .option('--passwd <password>', 'Password to generate this key\'s secret key')
-  .option('--participate <Y|N>', 'Participate to writing the blockchain')
-  .option('--cpu <percent>', 'Percent of CPU usage for proof-of-work computation', parsePercent)
-
-  .option('-c, --currency <name>', 'Name of the currency managed by this node.')
-  .option('--sigPeriod <timestamp>', 'Minimum delay between 2 certifications of a same issuer, in seconds.')
-  .option('--sigStock <count>', 'Maximum quantity of valid certifications per member.')
-  .option('--sigWindow <duration>', 'Maximum age of a non-written certification.')
-  .option('--idtyWindow <duration>', 'Maximum age of a non-written certification.')
-  .option('--sigValidity <timestamp>', 'Validity duration of a certification, in seconds.')
-  .option('--msValidity <timestamp>', 'Validity duration of a memberships, in seconds.')
-  .option('--sigQty <number>', 'Minimum number of required certifications to be a member/stay as a member')
-  .option('--medtblocks <number>', 'medianTimeBlocks parameter of UCP')
-  .option('--avgGenTime <number>', 'avgGenTime parameter of UCP')
-  .option('--dtdiffeval <number>', 'dtDiffEval parameter of UCP')
-  .option('--powZeroMin <number>', 'Minimum number of leading zeros for a proof-of-work')
-  .option('--powPeriod <number>', 'Number of blocks to wait to decrease proof-of-work difficulty by one')
-  .option('--powDelay <number>', 'Number of seconds to wait before starting the computation of next block')
-  .option('--growth <number>', 'Universal Dividend %growth. Aka. \'c\' parameter in RTM', parsePercent)
-  .option('--ud0 <number>', 'Universal Dividend initial value')
-  .option('--dt <number>', 'Number of seconds between two UD')
-  .option('--rootoffset <number>', 'Allow to give a time offset for first block (offset in the past)')
-  .option('--show', 'With gen-next or gen-root commands, displays the generated block')
-
-  .option('--nointeractive', 'Disable interactive sync UI')
-  .option('--nocautious', 'Do not check blocks validity during sync')
-  .option('--cautious', 'Check blocks validity during sync (overrides --nocautious option)')
-  .option('--nopeers', 'Do not retrieve peers during sync')
-  .option('--nostdout', 'Disable stdout printing for `export-bc` command')
-  .option('--noshuffle', 'Disable peers shuffling for `sync` command')
-
-  .option('--timeout <milliseconds>', 'Timeout to use when contacting peers', parseInt)
-  .option('--httplogs', 'Enable HTTP logs')
-  .option('--nohttplogs', 'Disable HTTP logs')
-  .option('--isolate', 'Avoid the node to send peering or status informations to the network')
-  .option('--check', 'With gen-next: just check validity of generated block')
-  .option('--forksize <size>', 'Maximum size of fork window', parseInt)
-  .option('--memory', 'Memory mode')
-;
-
-program
-  .command('start')
-  .description('Start Duniter node daemon.')
-  .action(subCommand(service((server, conf) => new Promise((resolve, reject) => {
-    co(function*() {
-        try {
-          const bma = require('./lib/streams/bma');
+      program
+        .command('start')
+        .description('Start Duniter node daemon.')
+        .action(subCommand(service((server, conf) => new Promise((resolve, reject) => {
+          co(function*() {
+            try {
+              const bma = require('./lib/streams/bma');
 
-          logger.info(">> NODE STARTING");
+              logger.info(">> NODE STARTING");
 
-          // Public http interface
-          let bmapi = yield bma(server, null, conf.httplogs);
+              // Public http interface
+              let bmapi = yield bma(server, null, conf.httplogs);
 
-          // Routing documents
-          server.routing();
+              // Routing documents
+              server.routing();
 
-          // Services
-          yield server.startServices();
-          yield bmapi.openConnections();
+              // Services
+              yield server.startServices();
+              yield bmapi.openConnections();
 
-          logger.info('>> Server ready!');
+              logger.info('>> Server ready!');
 
-        } catch (e) {
-          reject(e);
-        }
-    });
-  }))));
-
-program
-  .command('stop')
-  .description('Stop Duniter node daemon.')
-  .action(subCommand(needsToBeLaunchedByScript));
-
-program
-  .command('restart')
-  .description('Restart Duniter node daemon.')
-  .action(subCommand(needsToBeLaunchedByScript));
-
-program
-  .command('wizard [step]')
-  .description('Launch the configuration wizard.')
-  .action(subCommand(function (step) {
-    // Only show message "Saved"
-    return connect(function (step, server, conf) {
-      return new Promise((resolve, reject) => {
-        async.series([
-          function (next) {
-            startWizard(step, server, conf, next);
+            } catch (e) {
+              reject(e);
+            }
+          });
+        }))));
+
+      program
+        .command('stop')
+        .description('Stop Duniter node daemon.')
+        .action(subCommand(needsToBeLaunchedByScript));
+
+      program
+        .command('restart')
+        .description('Restart Duniter node daemon.')
+        .action(subCommand(needsToBeLaunchedByScript));
+
+      program
+        .command('wizard [step]')
+        .description('Launch the configuration wizard.')
+        .action(subCommand(function (step) {
+          // Only show message "Saved"
+          return connect(function (step, server, conf) {
+            return new Promise((resolve, reject) => {
+              async.series([
+                function (next) {
+                  startWizard(service, step, server, conf, next);
+                }
+              ], (err) => {
+                if (err) return reject(err);
+                resolve();
+              });
+            });
+          })(step, null);
+        }));
+
+      program
+        .command('sync [host] [port] [to]')
+        .description('Synchronize blockchain from a remote Duniter node')
+        .action(subCommand(service(function (host, port, to, server, conf) {
+          if (!host) {
+            throw 'Host is required.';
+          }
+          if (!port) {
+            throw 'Port is required.';
+          }
+          return co(function *() {
+            let cautious;
+            if (program.nocautious) {
+              cautious = false;
+            }
+            if (program.cautious) {
+              cautious = true;
+            }
+            yield server.synchronize(host, port, parseInt(to), 0, !program.nointeractive, cautious, program.nopeers, program.noshuffle);
+            if (server) {
+              yield server.disconnect();
+            }
+          });
+        })));
+
+      program
+        .command('peer [host] [port]')
+        .description('Exchange peerings with another node')
+        .action(subCommand(service(function (host, port, server) {
+          return co(function *() {
+            try {
+              logger.info('Fetching peering record at %s:%s...', host, port);
+              let peering = yield contacter.statics.fetchPeer(host, port);
+              logger.info('Apply peering ...');
+              yield server.PeeringService.submitP(peering, ERASE_IF_ALREADY_RECORDED, !program.nocautious);
+              logger.info('Applied');
+              let selfPeer = yield server.dal.getPeer(server.PeeringService.pubkey);
+              if (!selfPeer) {
+                yield Q.nfcall(server.PeeringService.generateSelfPeer, server.conf, 0);
+                selfPeer = yield server.dal.getPeer(server.PeeringService.pubkey);
+              }
+              logger.info('Send self peering ...');
+              var caster = multicaster();
+              yield caster.sendPeering(Peer.statics.peerize(peering), Peer.statics.peerize(selfPeer));
+              logger.info('Sent.');
+              yield server.disconnect();
+            } catch(e) {
+              logger.error(e.code || e.message || e);
+              throw Error("Exiting");
+            }
+          });
+        })));
+
+      program
+        .command('revert [count]')
+        .description('Revert (undo + remove) the top [count] blocks from the blockchain. EXPERIMENTAL')
+        .action(subCommand(service(function (count, server) {
+          return co(function *() {
+            try {
+              for (let i = 0; i < count; i++) {
+                yield server.revert();
+              }
+            } catch (err) {
+              logger.error('Error during revert:', err);
+            }
+            // Save DB
+            yield server.disconnect();
+          });
+        })));
+
+      program
+        .command('revert-to [number]')
+        .description('Revert (undo + remove) top blockchain blocks until block #[number] is reached. EXPERIMENTAL')
+        .action(subCommand(service(function (number, server) {
+          return co(function *() {
+            try {
+              yield server.revertTo(number);
+            } catch (err) {
+              logger.error('Error during revert:', err);
+            }
+            // Save DB
+            if (server) {
+              yield server.disconnect();
+            }
+          });
+        })));
+
+      program
+        .command('reapply-to [number]')
+        .description('Reapply reverted blocks until block #[number] is reached. EXPERIMENTAL')
+        .action(subCommand(service(function (number, server) {
+          return co(function *() {
+            try {
+              yield server.reapplyTo(number);
+            } catch (err) {
+              logger.error('Error during reapply:', err);
+            }
+            // Save DB
+            if (server) {
+              yield server.disconnect();
+            }
+          });
+        })));
+
+      program
+        .command('gen-next [host] [port] [difficulty]')
+        .description('Tries to generate the next block of the blockchain')
+        .action(subCommand(service(generateAndSend(program, (server) => server.BlockchainService.generateNext))));
+
+      program
+        .command('gen-root [host] [port] [difficulty]')
+        .description('Tries to generate root block, with choice of root members')
+        .action(subCommand(service(function (host, port, difficulty, server, conf) {
+          if (!host) {
+            throw 'Host is required.';
+          }
+          if (!port) {
+            throw 'Port is required.';
           }
-        ], (err) => {
-          if (err) return reject(err);
-          resolve();
+          if (!difficulty) {
+            throw 'Difficulty is required.';
+          }
+          return generateAndSend(program, (server) => server.BlockchainService.generateManualRoot)(host, port, difficulty, server, conf);
+        })));
+
+      program
+        .command('export-bc [upto]')
+        .description('Exports the whole blockchain as JSON array, up to [upto] block number (excluded).')
+        .action(subCommand(service(function (upto, server) {
+          return co(function *() {
+            try {
+              let CHUNK_SIZE = 500;
+              let jsoned = [];
+              let current = yield server.dal.getCurrentBlockOrNull();
+              let lastNumber = current ? current.number + 1 : -1;
+              if (upto !== undefined && upto.match(/\d+/)) {
+                lastNumber = Math.min(parseInt(upto), lastNumber);
+              }
+              let chunksCount = Math.floor(lastNumber / CHUNK_SIZE);
+              let chunks = [];
+              // Max-size chunks
+              for (let i = 0, len = chunksCount; i < len; i++) {
+                chunks.push({start: i * CHUNK_SIZE, to: i * CHUNK_SIZE + CHUNK_SIZE - 1});
+              }
+              // A last chunk
+              if (lastNumber > chunksCount * CHUNK_SIZE) {
+                chunks.push({start: chunksCount * CHUNK_SIZE, to: lastNumber});
+              }
+              for (const chunk of chunks) {
+                let blocks = yield server.dal.getBlocksBetween(chunk.start, chunk.to);
+                blocks.forEach(function (block) {
+                  jsoned.push(_(new Block(block).json()).omit('raw'));
+                });
+              }
+              if (!program.nostdout) {
+                console.log(JSON.stringify(jsoned, null, "  "));
+              }
+              yield server.disconnect();
+              return jsoned;
+            } catch(err) {
+              logger.warn(err.message || err);
+              yield server.disconnect();
+            }
+          });
+        }, NO_LOGS)));
+
+      program
+        .command('check-config')
+        .description('Checks the node\'s configuration')
+        .action(subCommand(service(function (server) {
+          return server.checkConfig()
+            .then(function () {
+              logger.warn('Configuration seems correct.');
+            })
+        })));
+
+      program
+        .command('reset [config|data|peers|tx|stats|all]')
+        .description('Reset configuration, data, peers, transactions or everything in the database')
+        .action(subCommand((type) => {
+          let init = ['data', 'all'].indexOf(type) !== -1 ? server.bind(server, program) : connect;
+          return init(function (server) {
+            if (!~['config', 'data', 'peers', 'stats', 'all'].indexOf(type)) {
+              throw constants.ERRORS.CLI_CALLERR_RESET;
+            }
+            return co(function*() {
+              try {
+                if (type == 'data') {
+                  yield server.resetData();
+                  logger.warn('Data successfully reseted.');
+                }
+                if (type == 'peers') {
+                  yield server.resetPeers();
+                  logger.warn('Peers successfully reseted.');
+                }
+                if (type == 'stats') {
+                  yield server.resetStats();
+                  logger.warn('Stats successfully reseted.');
+                }
+                if (type == 'config') {
+                  yield server.resetConf();
+                  logger.warn('Configuration successfully reseted.');
+                }
+                if (type == 'all') {
+                  yield server.resetAll();
+                  logger.warn('Data & Configuration successfully reseted.');
+                }
+              } catch (e) {
+                logger.error(e);
+              }
+            });
+          }, type != 'peers')(type);
+        }));
+
+      program
+        .on('*', function (cmd) {
+          console.log("Unknown command '%s'. Try --help for a listing of commands & options.", cmd);
+          onResolve();
         });
-      });
-    })(step, null);
-  }));
-
-program
-  .command('sync [host] [port] [to]')
-  .description('Synchronize blockchain from a remote Duniter node')
-  .action(subCommand(service(function (host, port, to, server, conf) {
-    if (!host) {
-      throw 'Host is required.';
-    }
-    if (!port) {
-      throw 'Port is required.';
-    }
-    return co(function *() {
-      let cautious;
-      if (program.nocautious) {
-        cautious = false;
-      }
-      if (program.cautious) {
-        cautious = true;
-      }
-      yield server.synchronize(host, port, parseInt(to), 0, !program.nointeractive, cautious, program.nopeers, program.noshuffle);
-      if (server) {
-        yield server.disconnect();
-      }
-    });
-  })));
-
-program
-  .command('peer [host] [port]')
-  .description('Exchange peerings with another node')
-  .action(subCommand(service(function (host, port, server) {
-    return co(function *() {
-      try {
-        logger.info('Fetching peering record at %s:%s...', host, port);
-        let peering = yield contacter.statics.fetchPeer(host, port);
-        logger.info('Apply peering ...');
-        yield server.PeeringService.submitP(peering, ERASE_IF_ALREADY_RECORDED, !program.nocautious);
-        logger.info('Applied');
-        let selfPeer = yield server.dal.getPeer(server.PeeringService.pubkey);
-        if (!selfPeer) {
-          yield Q.nfcall(server.PeeringService.generateSelfPeer, server.conf, 0);
-          selfPeer = yield server.dal.getPeer(server.PeeringService.pubkey);
-        }
-        logger.info('Send self peering ...');
-        var caster = multicaster();
-        yield caster.sendPeering(Peer.statics.peerize(peering), Peer.statics.peerize(selfPeer));
-        logger.info('Sent.');
-        yield server.disconnect();
-      } catch(e) {
-        logger.error(e.code || e.message || e);
-        throw Error("Exiting");
-      }
-    });
-  })));
-
-program
-  .command('revert [count]')
-  .description('Revert (undo + remove) the top [count] blocks from the blockchain. EXPERIMENTAL')
-  .action(subCommand(service(function (count, server) {
-    return co(function *() {
-      try {
-        for (let i = 0; i < count; i++) {
-          yield server.revert();
-        }
-      } catch (err) {
-        logger.error('Error during revert:', err);
-      }
-      // Save DB
-      yield server.disconnect();
-    });
-  })));
-
-program
-  .command('revert-to [number]')
-  .description('Revert (undo + remove) top blockchain blocks until block #[number] is reached. EXPERIMENTAL')
-  .action(subCommand(service(function (number, server) {
-    return co(function *() {
-      try {
-        yield server.revertTo(number);
-      } catch (err) {
-        logger.error('Error during revert:', err);
-      }
-      // Save DB
-      if (server) {
-        yield server.disconnect();
-      }
-    });
-  })));
-
-program
-  .command('reapply-to [number]')
-  .description('Reapply reverted blocks until block #[number] is reached. EXPERIMENTAL')
-  .action(subCommand(service(function (number, server) {
-    return co(function *() {
-      try {
-        yield server.reapplyTo(number);
-      } catch (err) {
-        logger.error('Error during reapply:', err);
+
+      function subCommand(promiseFunc) {
+        return function() {
+          let args = Array.prototype.slice.call(arguments, 0);
+          return co(function*() {
+            try {
+              let result = yield promiseFunc.apply(null, args);
+              onResolve(result);
+            } catch (e) {
+              if (e && e.uerr) {
+                onReject(e.uerr.message);
+              } else {
+                onReject(e);
+              }
+            }
+          })
+        };
       }
-      // Save DB
-      if (server) {
-        yield server.disconnect();
+
+      function connect(callback, useDefaultConf) {
+        return function () {
+          var cbArgs = arguments;
+          var dbName = program.mdb || "duniter_default";
+          var dbHome = program.home;
+
+          const home = directory.getHome(dbName, dbHome);
+          var server = duniter(home, program.memory === true, commandLineConf(program));
+
+          // If ever the process gets interrupted
+          let isSaving = false;
+          closeCommand = () => co(function*() {
+            if (!isSaving) {
+              isSaving = true;
+              // Save DB
+              return server.disconnect();
+            }
+          });
+
+          // Initialize server (db connection, ...)
+          return server.plugFileSystem(useDefaultConf)
+            .then(() => server.loadConf())
+            .then(function () {
+              try {
+                cbArgs.length--;
+                cbArgs[cbArgs.length++] = server;
+                cbArgs[cbArgs.length++] = server.conf;
+                return callback.apply(this, cbArgs);
+              } catch(e) {
+                server.disconnect();
+                throw e;
+              }
+            });
+        };
       }
-    });
-  })));
-
-program
-  .command('gen-next [host] [port] [difficulty]')
-  .description('Tries to generate the next block of the blockchain')
-  .action(subCommand(service(generateAndSend((server) => server.BlockchainService.generateNext))));
-
-program
-  .command('gen-root [host] [port] [difficulty]')
-  .description('Tries to generate root block, with choice of root members')
-    .action(subCommand(service(function (host, port, difficulty, server, conf) {
-      if (!host) {
-        throw 'Host is required.';
+
+      function service(callback, nologs) {
+
+        return function () {
+
+          if (nologs) {
+            // Disable logs
+            require('../app/lib/logger')().mute();
+          }
+
+          var cbArgs = arguments;
+          var dbName = program.mdb;
+          var dbHome = program.home;
+
+          // Add log files for this instance
+          logger.addHomeLogs(directory.getHome(dbName, dbHome));
+
+          const home = directory.getHome(dbName, dbHome);
+          var server = duniter(home, program.memory === true, commandLineConf(program));
+
+          // If ever the process gets interrupted
+          let isSaving = false;
+          closeCommand = () => co(function*() {
+            if (!isSaving) {
+              isSaving = true;
+              // Save DB
+              return server.disconnect();
+            }
+          });
+
+          const that = this;
+
+          // Initialize server (db connection, ...)
+          return co(function*() {
+            try {
+              yield server.initWithDAL();
+              yield configure(program, server, server.conf || {});
+              yield server.loadConf();
+              cbArgs.length--;
+              cbArgs[cbArgs.length++] = server;
+              cbArgs[cbArgs.length++] = server.conf;
+              cbArgs[cbArgs.length++] = program;
+              onService && onService(server);
+              return callback.apply(that, cbArgs);
+            } catch (e) {
+              server.disconnect();
+              throw e;
+            }
+          });
+        };
       }
-      if (!port) {
-        throw 'Port is required.';
+
+      onService = onServiceCallback;
+      program.parse(programArgs);
+
+      if (programArgs.length <= 2) {
+        onReject('No command given.');
       }
-      if (!difficulty) {
-        throw 'Difficulty is required.';
+
+      const res = yield currentCommand;
+      if (closeCommand) {
+        yield closeCommand();
       }
-      return generateAndSend((server) => server.BlockchainService.generateManualRoot)(host, port, difficulty, server, conf);
-    })));
+      return res;
+    })
+  };
+};
+
+/****************
+ *
+ *   UTILITIES
+ *
+ ****************/
 
-function generateAndSend(getGenerationMethod) {
+function generateAndSend(program, getGenerationMethod) {
   return function (host, port, difficulty, server, conf) {
     return new Promise((resolve, reject) => {
       async.waterfall([
@@ -368,7 +578,7 @@ function generateAndSend(getGenerationMethod) {
                 });
               },
               function (pair, next) {
-                proveAndSend(server, block, pair.publicKey, parseInt(difficulty), host, parseInt(port), next);
+                proveAndSend(program, server, block, pair.publicKey, parseInt(difficulty), host, parseInt(port), next);
               }
             ], next);
           }
@@ -381,7 +591,7 @@ function generateAndSend(getGenerationMethod) {
   };
 }
 
-function proveAndSend(server, block, issuer, difficulty, host, port, done) {
+function proveAndSend(program, server, block, issuer, difficulty, host, port, done) {
   var BlockchainService = server.BlockchainService;
   async.waterfall([
     function (next) {
@@ -414,101 +624,7 @@ function proveAndSend(server, block, issuer, difficulty, host, port, done) {
   ], done);
 }
 
-program
-  .command('export-bc [upto]')
-  .description('Exports the whole blockchain as JSON array, up to [upto] block number (excluded).')
-  .action(subCommand(service(function (upto, server) {
-    return co(function *() {
-      try {
-        let CHUNK_SIZE = 500;
-        let jsoned = [];
-        let current = yield server.dal.getCurrentBlockOrNull();
-        let lastNumber = current ? current.number + 1 : -1;
-        if (upto !== undefined && upto.match(/\d+/)) {
-          lastNumber = Math.min(parseInt(upto), lastNumber);
-        }
-        let chunksCount = Math.floor(lastNumber / CHUNK_SIZE);
-        let chunks = [];
-        // Max-size chunks
-        for (let i = 0, len = chunksCount; i < len; i++) {
-          chunks.push({start: i * CHUNK_SIZE, to: i * CHUNK_SIZE + CHUNK_SIZE - 1});
-        }
-        // A last chunk
-        if (lastNumber > chunksCount * CHUNK_SIZE) {
-          chunks.push({start: chunksCount * CHUNK_SIZE, to: lastNumber});
-        }
-        for (const chunk of chunks) {
-          let blocks = yield server.dal.getBlocksBetween(chunk.start, chunk.to);
-          blocks.forEach(function (block) {
-            jsoned.push(_(new Block(block).json()).omit('raw'));
-          });
-        }
-        if (!program.nostdout) {
-          console.log(JSON.stringify(jsoned, null, "  "));
-        }
-        yield server.disconnect();
-        return jsoned;
-      } catch(err) {
-          logger.warn(err.message || err);
-          yield server.disconnect();
-      }
-    });
-  }, NO_LOGS)));
-
-program
-  .command('check-config')
-  .description('Checks the node\'s configuration')
-  .action(subCommand(service(function (server) {
-    return server.checkConfig()
-      .then(function () {
-        logger.warn('Configuration seems correct.');
-      })
-  })));
-
-program
-  .command('config')
-  .description('Register configuration in database')
-  .action(subCommand(connect(configure)));
-
-program
-  .command('reset [config|data|peers|tx|stats|all]')
-  .description('Reset configuration, data, peers, transactions or everything in the database')
-  .action(subCommand((type) => {
-    let init = ['data', 'all'].indexOf(type) !== -1 ? server : connect;
-    return init(function (server) {
-      if (!~['config', 'data', 'peers', 'stats', 'all'].indexOf(type)) {
-        throw constants.ERRORS.CLI_CALLERR_RESET;
-      }
-      return co(function*() {
-        try {
-          if (type == 'data') {
-            yield server.resetData();
-            logger.warn('Data successfully reseted.');
-          }
-          if (type == 'peers') {
-            yield server.resetPeers();
-            logger.warn('Peers successfully reseted.');
-          }
-          if (type == 'stats') {
-            yield server.resetStats();
-            logger.warn('Stats successfully reseted.');
-          }
-          if (type == 'config') {
-            yield server.resetConf();
-            logger.warn('Configuration successfully reseted.');
-          }
-          if (type == 'all') {
-            yield server.resetAll();
-            logger.warn('Data & Configuration successfully reseted.');
-          }
-        } catch (e) {
-          logger.error(e);
-        }
-      });
-    }, type != 'peers')(type);
-  }));
-
-function startWizard(step, server, conf, done) {
+function startWizard(service, step, server, conf, done) {
   var wiz = wizard(server);
   var task = {
     'currency': wiz.configCurrency,
@@ -544,7 +660,7 @@ function startWizard(step, server, conf, done) {
   ], done);
 }
 
-function commandLineConf(conf) {
+function commandLineConf(program, conf) {
 
   conf = conf || {};
   conf.sync = conf.sync || {};
@@ -656,56 +772,21 @@ function commandLineConf(conf) {
   return _(conf).extend({routing: true});
 }
 
-function connect(callback, useDefaultConf) {
-  return function () {
-    var cbArgs = arguments;
-    var dbName = program.mdb || "duniter_default";
-    var dbHome = program.home;
-
-    const home = directory.getHome(dbName, dbHome);
-    var server = duniter(home, program.memory === true, commandLineConf());
-
-    // If ever the process gets interrupted
-    let isSaving = false;
-    closeCommand = () => co(function*() {
-      if (!isSaving) {
-        isSaving = true;
-        // Save DB
-        return server.disconnect();
-      }
-    });
-
-    // Initialize server (db connection, ...)
-    return server.plugFileSystem(useDefaultConf)
-      .then(() => server.loadConf())
-      .then(function () {
-        try {
-          cbArgs.length--;
-          cbArgs[cbArgs.length++] = server;
-          cbArgs[cbArgs.length++] = server.conf;
-          return callback.apply(this, cbArgs);
-        } catch(e) {
-          server.disconnect();
-          throw e;
-	}
-      });
-  };
-}
-
 /**
  * Super basic server with only its home path set
+ * @param program
  * @param callback
  * @param useDefaultConf
  * @returns {Function}
  */
-function server(callback, useDefaultConf) {
+function server(program, callback, useDefaultConf) {
   return function () {
     var cbArgs = arguments;
     var dbName = program.mdb || "duniter_default";
     var dbHome = program.home;
 
     const home = directory.getHome(dbName, dbHome);
-    var server = duniter(home, program.memory === true, commandLineConf());
+    var server = duniter(home, program.memory === true, commandLineConf(program));
 
     cbArgs.length--;
     cbArgs[cbArgs.length++] = server;
@@ -714,86 +795,17 @@ function server(callback, useDefaultConf) {
   };
 }
 
-function service(callback, nologs) {
-
-  return function () {
-
-    if (nologs) {
-      // Disable logs
-      require('../app/lib/logger')().mute();
-    }
-
-    var cbArgs = arguments;
-    var dbName = program.mdb;
-    var dbHome = program.home;
-
-    // Add log files for this instance
-    logger.addHomeLogs(directory.getHome(dbName, dbHome));
-
-    const home = directory.getHome(dbName, dbHome);
-    var server = duniter(home, program.memory === true, commandLineConf());
-
-    // If ever the process gets interrupted
-    let isSaving = false;
-    closeCommand = () => co(function*() {
-      if (!isSaving) {
-        isSaving = true;
-        // Save DB
-        return server.disconnect();
-      }
-    });
-
-    const that = this;
-
-    // Initialize server (db connection, ...)
-    return co(function*() {
-      try {
-        yield server.initWithDAL();
-        yield configure(server, server.conf || {});
-        yield server.loadConf();
-        cbArgs.length--;
-        cbArgs[cbArgs.length++] = server;
-        cbArgs[cbArgs.length++] = server.conf;
-        cbArgs[cbArgs.length++] = program;
-        onService && onService(server);
-        return callback.apply(that, cbArgs);
-      } catch (e) {
-        server.disconnect();
-        throw e;
-      }
-    });
-  };
-}
-
 function parsePercent(s) {
   var f = parseFloat(s);
   return isNaN(f) ? 0 : f;
 }
 
-program
-  .on('*', function (cmd) {
-    console.log("Unknown command '%s'. Try --help for a listing of commands & options.", cmd);
-    throw Error("Exiting");
-  });
-
-module.exports.addCommand = (command, requirements, promiseCallback) => {
-  program
-    .command(command.name)
-    .description(command.desc)
-    .action(subCommand(service(promiseCallback)));
-};
-
-module.exports.addOption = (optFormat, optDesc, optParser) => {
-  program
-    .option(optFormat, optDesc, optParser);
-};
-
 function needsToBeLaunchedByScript() {
     logger.error('This command must not be launched directly, using duniter.sh script');
     return Promise.resolve();
 }
 
-function configure(server, conf) {
+function configure(program, server, conf) {
   return co(function *() {
     if (typeof server == "string" || typeof conf == "string") {
       throw constants.ERRORS.CLI_CALLERR_CONFIG;
diff --git a/app/lib/dal/fileDAL.js b/app/lib/dal/fileDAL.js
index 111ebbc6e282df437f043096382db0d315a2f5d4..c8b7c1f015a6657ece5cf3171c1d31904e3f5c09 100644
--- a/app/lib/dal/fileDAL.js
+++ b/app/lib/dal/fileDAL.js
@@ -686,10 +686,16 @@ function FileDAL(params) {
   });
 
   this.saveConf = (confToSave) => {
-    // TODO: Do something about the currency global variable
-    currency = confToSave.currency;
-    // Save the conf in file
-    return that.confDAL.saveConf(confToSave);
+    return co(function*() {
+      // TODO: Do something about the currency global variable
+      currency = confToSave.currency;
+      // Save the conf in file
+      let theConf = confToSave;
+      if (that.saveConfHook) {
+        theConf = yield that.saveConfHook(theConf);
+      }
+      return that.confDAL.saveConf(theConf);
+    });
   };
 
   /***********************
diff --git a/bin/duniter b/bin/duniter
index cc6a71a91ee649d1bace05fd873a9b323c4b4ee3..153524c40864e8aa710a83505ec03f7d031bf916 100755
--- a/bin/duniter
+++ b/bin/duniter
@@ -5,20 +5,27 @@ const co = require('co');
 const duniter = require('../index');
 const stack = duniter.statics.autoStack();
 
-stack.registerDependency({
-  duniter: {
-    cli: [{
-      name: 'hello',
-      desc: 'Says hello to the world.',
-      requires: ['service'],
-      promiseCallback: (duniterServer) => co(function*(){
-        console.log('Hello, world.');
-      })
-    }]
-  }
-});
+return co(function*() {
 
-return co(function*(){
-  yield stack.executeStack();
-  console.log('Done');
+  // Specific errors handling
+  process.on('uncaughtException', (err) => {
+    // Dunno why this specific exception is not caught
+    if (err.code !== "EADDRNOTAVAIL" && err.code !== "EINVAL") {
+      duniter.statics.logger.error(err);
+      process.exit(2);
+    }
+  });
+
+  try {
+    yield stack.executeStack(process.argv);
+    // Everything went well, close Duniter quietly.
+    process.exit();
+  } catch (e) {
+    // If an unhandled error occured
+    duniter.statics.logger.error(e);
+    process.exit(1);
+  } finally {
+    // If we did not succeed to close before, force close with error.
+    process.exit(100);
+  }
 });
diff --git a/index.js b/index.js
index 76df86eb0afb33a1c4115e0102d2b6c14f9df58c..9eddb7a6de43e73316cfc26770d3f577a787a204 100644
--- a/index.js
+++ b/index.js
@@ -1,10 +1,30 @@
 "use strict";
 
+const Q = require('q');
 const co = require('co');
+const util = require('util');
+const stream = require('stream');
 const _ = require('underscore');
 const Server = require('./server');
+const directory = require('./app/lib/system/directory');
+const constants = require('./app/lib/constants');
+const wizard = require('./app/lib/wizard');
 const logger = require('./app/lib/logger')('duniter');
 
+
+const configDependency = {
+  duniter: {
+    cli: [{
+      name: 'config',
+      desc: 'Register configuration in database',
+      // The command does nothing particular, it just stops the process right after configuration phase is over
+      onConfiguredExecute: (server, conf, program, params) => Promise.resolve(conf)
+    }]
+  }
+};
+
+const DEFAULT_DEPENDENCIES = [configDependency];
+
 module.exports = function (home, memory, overConf) {
   return new Server(home, memory, overConf);
 };
@@ -13,121 +33,426 @@ module.exports.statics = {
 
   logger: logger,
 
-  /**************
-   * Duniter used by its Command Line Interface
-   * @param onService A callback for external usage when Duniter server is ready
+  /**
+   * Creates a new stack with core registrations only.
    */
-  cli: (onService) => {
+  simpleStack: () => new Stack(DEFAULT_DEPENDENCIES),
 
-    const cli = require('./app/cli');
+  /**
+   * Creates a new stack pre-registered with compliant modules found in package.json
+   */
+  autoStack: () => {
+    const pjson = require('./package.json');
+    const duniterModules = [];
 
-    // Specific errors handling
-    process.on('uncaughtException', (err) => {
-      // Dunno why this specific exception is not caught
-      if (err.code !== "EADDRNOTAVAIL" && err.code !== "EINVAL") {
-        logger.error(err);
-        process.exit(1);
+    // Look for compliant packages
+    const prodDeps = Object.keys(pjson.dependencies);
+    const devDeps = Object.keys(pjson.devDependencies);
+    const duniterDeps = _.filter(prodDeps.concat(devDeps), (dep) => dep.match(/^duniter-/));
+    for(const dep of duniterDeps) {
+      const required = require(dep);
+      if (required.duniter) {
+        duniterModules.push({
+          name: dep,
+          required
+        });
       }
-    });
+    }
 
-    process.on('unhandledRejection', (reason) => {
-      logger.error('Unhandled rejection: ' + reason);
-    });
+    // The dependencies found in package.json
+    const foundDependencies = duniterModules.map(duniterModule => duniterModule.required);
 
-    return co(function*() {
-      try {
-        // Prepare the command
-        const command = cli(process.argv);
-        // If ever the process gets interrupted
-        process.on('SIGINT', () => {
-          co(function*() {
-            yield command.closeCommand();
-            process.exit();
-          });
-        });
-        // Executes the command
-        yield command.execute(onService);
-        process.exit();
-      } catch (e) {
-        logger.error(e);
-        process.exit(1);
+    // The final stack
+    return new Stack(DEFAULT_DEPENDENCIES.concat(foundDependencies));
+  }
+};
+
+function Stack(dependencies) {
+
+  const that = this;
+  const cli = require('./app/cli')();
+  const configLoadingCallbacks = [];
+  const configBeforeSaveCallbacks = [];
+  const INPUT = new InputStream();
+  const PROCESS = new ProcessStream();
+
+  const streams = {
+    input: [],
+    process: [],
+    output: [],
+  };
+
+  this.registerDependency = (requiredObject) => {
+    const def = requiredObject.duniter;
+    for (const opt of (def.cliOptions || [])) {
+      cli.addOption(opt.value, opt.desc, opt.parser);
+    }
+    for (const command of (def.cli || [])) {
+      cli.addCommand({
+        name: command.name,
+        desc: command.desc
+      }, (...args) => that.processCommand.apply(null, [command].concat(args)));
+    }
+
+    /**
+     * Configuration injection
+     * -----------------------
+     */
+    if (def.config) {
+      if (def.config.onLoading) {
+        configLoadingCallbacks.push(def.config.onLoading);
       }
-    });
+      // Before the configuration is saved, the module can make some injection/cleaning
+      if (def.config.beforeSave) {
+        configBeforeSaveCallbacks.push(def.config.beforeSave);
+      }
+    }
 
-  },
+    /**
+     * Service injection
+     * -----------------
+     */
+    if (def.service) {
+      // To feed data coming from some I/O (network, disk, other module, ...)
+      if (def.service.input) {
+        streams.input.push(def.service.input);
+      }
+      // To handle data that has been submitted by INPUT stream
+      if (def.service.process) {
+        streams.process.push(def.service.process);
+      }
+      // To handle data that has been validated by PROCESS stream
+      if (def.service.output) {
+        streams.output.push(def.service.output);
+      }
+    }
+  };
 
-  autoStack: () => {
+  this.processCommand = (...args) => co(function*() {
+    const command = args[0];
+    const program = args[1];
+    const params  = args.slice(2);
+    params.pop(); // Don't need the command argument
+
+    const dbName = program.mdb;
+    const dbHome = program.home;
+    const home = directory.getHome(dbName, dbHome);
+
+    // Add log files for this instance
+    logger.addHomeLogs(home);
+
+    const server = new Server(home, program.memory === true, commandLineConf(program));
+
+    // If ever the process gets interrupted
+    let isSaving = false;
+    process.on('SIGINT', () => {
+      co(function*() {
+        if (!isSaving) {
+          isSaving = true;
+          // Save DB
+          return server.disconnect();
+        }
+        process.exit();
+      });
+    });
 
-    const cli = require('./app/cli');
-    const stack = {
+    // Initialize server (db connection, ...)
+    try {
+      yield server.plugFileSystem();
 
-      registerDependency: (requiredObject) => {
-        for (const opt of (requiredObject.duniter.cliOptions || [])) {
-          cli.addOption(opt.value, opt.desc, opt.parser);
+      // Register the configuration hook for loading phase (overrides the loaded data)
+      server.loadConfHook = (conf) => co(function*() {
+        // Loading injection
+        for (const callback of configLoadingCallbacks) {
+          yield callback(conf, program);
         }
-        for (const command of (requiredObject.duniter.cli || [])) {
-          cli.addCommand({ name: command.name, desc: command.desc }, command.requires, command.promiseCallback);
+      });
+
+      // Register the configuration hook for saving phase (overrides the saved data)
+      server.dal.saveConfHook = (conf) => co(function*() {
+        const clonedConf = _.clone(conf);
+        for (const callback of configBeforeSaveCallbacks) {
+          yield callback(clonedConf, program);
         }
-      },
+        return clonedConf;
+      });
 
-      executeStack: () => {
+      const conf = yield server.loadConf();
+      // Auto-configuration default
+      yield configure(program, server, server.conf || {});
+      // Autosave conf
+      try {
+        yield server.dal.saveConf(conf);
+        logger.debug("Configuration saved.");
+      } catch (e) {
+        logger.error("Configuration could not be saved: " + e);
+        throw Error(e);
+      }
+      // First possible class of commands: post-config
+      if (command.onConfiguredExecute) {
+        return yield command.onConfiguredExecute(server, conf, program, params);
+      }
+      // Second possible class of commands: post-service
+      yield server.initDAL();
+      return yield command.onPluggedDALExecute(server, conf, program, params,
 
-        // Specific errors handling
-        process.on('uncaughtException', (err) => {
-          // Dunno why this specific exception is not caught
-          if (err.code !== "EADDRNOTAVAIL" && err.code !== "EINVAL") {
-            logger.error(err);
-            process.exit(1);
+        // Start services and streaming between them
+        () => {
+          const modules = streams.input.concat(streams.process).concat(streams.output);
+          for (const module of modules) {
+            // Any streaming module must implement a `startService` method
+            module.startService();
           }
-        });
-
-        process.on('unhandledRejection', (reason) => {
-          logger.error('Unhandled rejection: ' + reason);
-        });
+          // All inputs write to global INPUT stream
+          for (const module of streams.input) module.pipe(INPUT);
+          // All processes read from global INPUT stream
+          for (const module of streams.process) INPUT.pipe(module);
+          // All processes write to global PROCESS stream
+          for (const module of streams.process) module.pipe(PROCESS);
+          // All ouputs read from global PROCESS stream
+          for (const module of streams.process) PROCESS.pipe(module);
+        },
 
-        return co(function*() {
-          try {
-            // Prepare the command
-            const command = cli(process.argv);
-            // If ever the process gets interrupted
-            process.on('SIGINT', () => {
-              co(function*() {
-                yield command.closeCommand();
-                process.exit();
-              });
-            });
-            // Executes the command
-            yield command.execute();
-            process.exit();
-          } catch (e) {
-            logger.error(e);
-            process.exit(1);
+        // Stop services and streaming between them
+        () => {
+          const modules = streams.input.concat(streams.process).concat(streams.output);
+          for (const module of modules) {
+            // Any streaming module must implement a `stopService` method
+            module.stopService();
           }
+          // Stop reading inputs
+          for (const module of streams.input) module.unpipe();
+          // Stop reading from global INPUT
+          INPUT.unpipe();
+          for (const module of streams.process) module.unpipe();
+          // Stop reading from global PROCESS
+          PROCESS.unpipe();
         });
+    } catch (e) {
+      server.disconnect();
+      throw e;
+    }
+  });
+
+  this.executeStack = (argv) => {
+
+    // Trace these errors
+    process.on('unhandledRejection', (reason) => {
+      logger.error('Unhandled rejection: ' + reason);
+    });
+
+    // Executes the command
+    return cli.execute(argv);
+  };
+
+  // We register the initial dependencies right now. Others can be added thereafter.
+  for (const dep of dependencies) {
+    that.registerDependency(dep);
+  }
+}
+
+function commandLineConf(program, conf) {
+
+  conf = conf || {};
+  conf.sync = conf.sync || {};
+  var cli = {
+    currency: program.currency,
+    cpu: program.cpu,
+    server: {
+      port: program.port,
+      ipv4address: program.ipv4,
+      ipv6address: program.ipv6,
+      salt: program.salt,
+      passwd: program.passwd,
+      remote: {
+        host: program.remoteh,
+        ipv4: program.remote4,
+        ipv6: program.remote6,
+        port: program.remotep
       }
-    };
+    },
+    db: {
+      mport: program.mport,
+      mdb: program.mdb,
+      home: program.home
+    },
+    net: {
+      upnp: program.upnp,
+      noupnp: program.noupnp
+    },
+    logs: {
+      http: program.httplogs,
+      nohttp: program.nohttplogs
+    },
+    endpoints: [],
+    rmEndpoints: [],
+    ucp: {
+      rootoffset: program.rootoffset,
+      sigPeriod: program.sigPeriod,
+      sigStock: program.sigStock,
+      sigWindow: program.sigWindow,
+      idtyWindow: program.idtyWindow,
+      msWindow: program.msWindow,
+      sigValidity: program.sigValidity,
+      sigQty: program.sigQty,
+      msValidity: program.msValidity,
+      powZeroMin: program.powZeroMin,
+      powPeriod: program.powPeriod,
+      powDelay: program.powDelay,
+      participate: program.participate,
+      ud0: program.ud0,
+      c: program.growth,
+      dt: program.dt,
+      incDateMin: program.incDateMin,
+      medtblocks: program.medtblocks,
+      dtdiffeval: program.dtdiffeval,
+      avgGenTime: program.avgGenTime
+    },
+    isolate: program.isolate,
+    forksize: program.forksize,
+    nofork: program.nofork,
+    timeout: program.timeout
+  };
 
-    const pjson = require('./package.json');
-    const duniterModules = [];
+  // Update conf
+  if (cli.currency)                         conf.currency = cli.currency;
+  if (cli.server.ipv4address)               conf.ipv4 = cli.server.ipv4address;
+  if (cli.server.ipv6address)               conf.ipv6 = cli.server.ipv6address;
+  if (cli.server.port)                      conf.port = cli.server.port;
+  if (cli.server.salt)                      conf.salt = cli.server.salt;
+  if (cli.server.passwd != undefined)       conf.passwd = cli.server.passwd;
+  if (cli.server.remote.host != undefined)  conf.remotehost = cli.server.remote.host;
+  if (cli.server.remote.ipv4 != undefined)  conf.remoteipv4 = cli.server.remote.ipv4;
+  if (cli.server.remote.ipv6 != undefined)  conf.remoteipv6 = cli.server.remote.ipv6;
+  if (cli.server.remote.port != undefined)  conf.remoteport = cli.server.remote.port;
+  if (cli.ucp.rootoffset)                   conf.rootoffset = cli.ucp.rootoffset;
+  if (cli.ucp.sigPeriod)                    conf.sigPeriod = cli.ucp.sigPeriod;
+  if (cli.ucp.sigStock)                     conf.sigStock = cli.ucp.sigStock;
+  if (cli.ucp.sigWindow)                    conf.sigWindow = cli.ucp.sigWindow;
+  if (cli.ucp.idtyWindow)                   conf.idtyWindow = cli.ucp.idtyWindow;
+  if (cli.ucp.msWindow)                     conf.msWindow = cli.ucp.msWindow;
+  if (cli.ucp.sigValidity)                  conf.sigValidity = cli.ucp.sigValidity;
+  if (cli.ucp.msValidity)                   conf.msValidity = cli.ucp.msValidity;
+  if (cli.ucp.sigQty)                       conf.sigQty = cli.ucp.sigQty;
+  if (cli.ucp.msValidity)                   conf.msValidity = cli.ucp.msValidity;
+  if (cli.ucp.powZeroMin)                   conf.powZeroMin = cli.ucp.powZeroMin;
+  if (cli.ucp.powPeriod)                    conf.powPeriod = cli.ucp.powPeriod;
+  if (cli.ucp.powDelay)                     conf.powDelay = cli.ucp.powDelay;
+  if (cli.ucp.participate)                  conf.participate = cli.ucp.participate == 'Y';
+  if (cli.ucp.dt)                           conf.dt = cli.ucp.dt;
+  if (cli.ucp.c)                            conf.c = cli.ucp.c;
+  if (cli.ucp.ud0)                          conf.ud0 = cli.ucp.ud0;
+  if (cli.ucp.incDateMin)                   conf.incDateMin = cli.ucp.incDateMin;
+  if (cli.ucp.medtblocks)                   conf.medianTimeBlocks = cli.ucp.medtblocks;
+  if (cli.ucp.avgGenTime)                   conf.avgGenTime = cli.ucp.avgGenTime;
+  if (cli.ucp.dtdiffeval)                   conf.dtDiffEval = cli.ucp.dtdiffeval;
+  if (cli.net.upnp)                         conf.upnp = true;
+  if (cli.net.noupnp)                       conf.upnp = false;
+  if (cli.cpu)                              conf.cpu = Math.max(0.01, Math.min(1.0, cli.cpu));
+  if (cli.logs.http)                        conf.httplogs = true;
+  if (cli.logs.nohttp)                      conf.httplogs = false;
+  if (cli.db.mport)                         conf.mport = cli.db.mport;
+  if (cli.db.home)                          conf.home = cli.db.home;
+  if (cli.db.mdb)                           conf.mdb = cli.db.mdb;
+  if (cli.isolate)                          conf.isolate = cli.isolate;
+  if (cli.timeout)                          conf.timeout = cli.timeout;
+  if (cli.forksize != null)                 conf.forksize = cli.forksize;
 
-    // Look for compliant packages
-    const prodDeps = Object.keys(pjson.dependencies);
-    const devDeps = Object.keys(pjson.devDependencies);
-    const duniterDeps = _.filter(prodDeps.concat(devDeps), (dep) => dep.match(/^duniter-/));
-    for(const dep of duniterDeps) {
-      const required = require(dep);
-      if (required.duniter) {
-        duniterModules.push({
-          name: dep,
-          required
-        });
+  // Specific internal settings
+  conf.createNext = true;
+  return _(conf).extend({routing: true});
+}
+
+function configure(program, server, conf) {
+  return co(function *() {
+    if (typeof server == "string" || typeof conf == "string") {
+      throw constants.ERRORS.CLI_CALLERR_CONFIG;
+    }
+    let wiz = wizard();
+    // UPnP override
+    if (program.noupnp === true) {
+      conf.upnp = false;
+    }
+    if (program.upnp === true) {
+      conf.upnp = true;
+    }
+    // Network autoconf
+    const autoconfNet = program.autoconf
+      || !(conf.ipv4 || conf.ipv6)
+      || !(conf.remoteipv4 || conf.remoteipv6 || conf.remotehost)
+      || !(conf.port && conf.remoteport);
+    if (autoconfNet) {
+      yield Q.nbind(wiz.networkReconfiguration, wiz)(conf, autoconfNet, program.noupnp);
+    }
+    const hasSaltPasswdKey = conf.salt && conf.passwd;
+    const hasKeyPair = conf.pair && conf.pair.pub && conf.pair.sec;
+    const autoconfKey = program.autoconf || (!hasSaltPasswdKey && !hasKeyPair);
+    if (autoconfKey) {
+      yield Q.nbind(wiz.keyReconfigure, wiz)(conf, autoconfKey);
+    }
+    // Try to add an endpoint if provided
+    if (program.addep) {
+      if (conf.endpoints.indexOf(program.addep) === -1) {
+        conf.endpoints.push(program.addep);
+      }
+      // Remove it from "to be removed" list
+      const indexInRemove = conf.rmEndpoints.indexOf(program.addep);
+      if (indexInRemove !== -1) {
+        conf.rmEndpoints.splice(indexInRemove, 1);
+      }
+    }
+    // Try to remove an endpoint if provided
+    if (program.remep) {
+      if (conf.rmEndpoints.indexOf(program.remep) === -1) {
+        conf.rmEndpoints.push(program.remep);
+      }
+      // Remove it from "to be added" list
+      const indexInToAdd = conf.endpoints.indexOf(program.remep);
+      if (indexInToAdd !== -1) {
+        conf.endpoints.splice(indexInToAdd, 1);
       }
     }
+  });
+}
+
+/**
+ * InputStream is a special stream that filters what passes in.
+ * Only DUP-like documents should be treated by the processing tools, to avoid JSON injection and save CPU cycles.
+ * @constructor
+ */
+function InputStream() {
+
+  const that = this;
 
-    for (const duniterModule of duniterModules) {
-      stack.registerDependency(duniterModule.required);
+  stream.Transform.call(this, { objectMode: true });
+
+  this._write = function (str, enc, done) {
+    if (typeof str === 'string') {
+      // Keep only strings
+      const matches = str.match(/Type: (.*)\n/);
+      if (matches && matches[0].match(/(Block|Membership|Identity|Certification|Transaction|Peer)/)) {
+        const type = matches[0].toLowerCase();
+        that.push({ type, doc: str });
+      }
     }
+    done && done();
+  };
+}
 
-    return stack;
-  }
-};
+function ProcessStream() {
+
+  const that = this;
+
+  stream.Transform.call(this, { objectMode: true });
+
+  this._write = function (obj, enc, done) {
+    // Never close the stream
+    if (obj !== undefined && obj !== null) {
+      that.push(obj);
+    }
+    done && done();
+  };
+}
+
+util.inherits(InputStream, stream.Transform);
+util.inherits(ProcessStream, stream.Transform);
diff --git a/server.js b/server.js
index 8c7218bb2b510fbcd72bb330f0e0046a9afcab4f..ff30ef2bc452329aa0cec06ccb46ec739f547765 100644
--- a/server.js
+++ b/server.js
@@ -141,6 +141,9 @@ function Server (home, memoryOnly, overrideConf) {
     else if (that.conf.passwd || that.conf.salt) {
       keyPair = yield keyring.scryptKeyPair(that.conf.salt, that.conf.passwd);
     }
+    if (that.loadConfHook) {
+      yield that.loadConfHook(that.conf);
+    }
     if (keyPair) {
       that.keyPair = keyPair;
       that.sign = keyPair.sign;
diff --git a/test/fast/v1.0-modules-api.js b/test/fast/v1.0-modules-api.js
new file mode 100644
index 0000000000000000000000000000000000000000..130b60b07888931b91dacbc94b4617137d6e2da6
--- /dev/null
+++ b/test/fast/v1.0-modules-api.js
@@ -0,0 +1,132 @@
+"use strict";
+
+const co      = require('co');
+const _       = require('underscore');
+const should  = require('should');
+const duniter = require('../../index');
+
+describe("v1.0 Module API", () => {
+
+  it('should be able to execute `hello` command', () => co(function*() {
+
+    const sStack = duniter.statics.simpleStack();
+    const aStack = duniter.statics.autoStack();
+
+    const helloDependency = {
+      duniter: {
+        cliOptions: [
+          { value: '--opt1', desc: 'The option 1. Enabled or not' },
+          { value: '--option2 <value>', desc: 'The option 2. Requires an argument, parsed as integer.', parser: parseInt }
+        ],
+        cli: [{
+          name: 'hello',
+          desc: 'Returns an "Hello, world" string after configuration phase.',
+          onConfiguredExecute: (server, conf, program, params) => co(function*(){
+            return "Hello, " + params[0] + ". You successfully sent arg '" + params[1] + "' along with opt1 = " + program.opt1 + " and option2 = " + program.option2 + ".";
+          })
+        }]
+      }
+    };
+
+    sStack.registerDependency(helloDependency);
+    aStack.registerDependency(helloDependency);
+
+    (yield sStack.executeStack(['node', 'index.js', 'hello', 'World', 'TEST', '--opt1', '--option2', '5'])).should.equal('Hello, World. You successfully sent arg \'TEST\' along with opt1 = true and option2 = 5.');
+    (yield aStack.executeStack(['node', 'index.js', 'hello', 'Zorld', 'ESSE', '--option2', 'd'])).should.equal('Hello, Zorld. You successfully sent arg \'ESSE\' along with opt1 = undefined and option2 = NaN.');
+  }));
+
+  /***********************
+   * CONFIGURATION HOOKS
+   **********************/
+
+  describe("Configuration hooks", () => {
+
+    let stack;
+    const run = (...args) => stack.executeStack(['node', 'index.js', '--mdb', 'modules_api_tests'].concat(args));
+
+    before(() => co(function*() {
+
+      stack = duniter.statics.simpleStack();
+      const configurationDependency = {
+        duniter: {
+          cliOptions: [
+            { value: '--supersalt <salt>', desc: 'A crypto salt.' },
+            { value: '--superpasswd <passwd>', desc: 'A crypto password.' }
+          ],
+          config: {
+            onLoading: (conf, program) => co(function*(){
+
+              // Always adds a parameter named "superkey"
+              conf.superkey = { pub: 'publicPart', sec: 'secretPart' };
+              // Eventually adds a supersalt if given as option
+              if (program.supersalt) {
+                conf.supersalt = program.supersalt;
+              }
+              // Eventually adds a superpasswd if given as option
+              if (program.superpasswd) {
+                conf.superpasswd = program.superpasswd;
+              }
+            }),
+            beforeSave: (conf, program) => co(function*(){
+
+              // We never want to store "superpasswd"
+              delete conf.superpasswd;
+            })
+          }
+        }
+      };
+      const returnConfDependency = {
+        duniter: {
+          cli: [{
+            name: 'gimme-conf',
+            desc: 'Returns the configuration object.',
+            onPluggedDALExecute: (server, conf, program, params, startServices, stopServices) => co(function*() {
+              // Gimme the conf!
+              return conf;
+            })
+          }],
+        }
+      };
+
+      stack.registerDependency(configurationDependency);
+      stack.registerDependency(returnConfDependency);
+    }));
+
+    it('verify that we get the CLI options', () => co(function*() {
+      const conf = yield run('gimme-conf', '--supersalt', 'NaCl');
+      conf.should.have.property('supersalt').equal('NaCl');
+    }));
+
+    it('verify that we get the saved options', () => co(function*() {
+      let conf;
+
+      // We make an initial reset
+      yield run('reset', 'config');
+      conf = yield run('gimme-conf');
+      conf.should.have.property('superkey'); // Always loaded
+      conf.should.not.have.property('supersalt');
+
+      // Nothing should have changed
+      conf = yield run('gimme-conf');
+      conf.should.have.property('superkey'); // Always loaded
+      conf.should.not.have.property('supersalt');
+
+      // Now we try to save the parameters
+      yield run('config', '--supersalt', 'NaCl2', '--superpasswd', 'megapasswd');
+      conf = yield run('gimme-conf');
+      conf.should.have.property('superkey'); // Always loaded
+      conf.should.have.property('supersalt').equal('NaCl2');
+      conf.should.not.have.property('superpasswd');
+
+      // Yet we can have all options by giving them explicitely using options
+      conf = yield run('gimme-conf', '--superpasswd', 'megapasswd2');
+      conf.should.have.property('superkey');
+      conf.should.have.property('supersalt').equal('NaCl2');
+      conf.should.have.property('superpasswd').equal('megapasswd2');
+    }));
+  });
+
+  // TODO: test serviceStart
+  // TODO: test serviceStop
+  // TODO: test streaming
+});
diff --git a/test/integration/cli.js b/test/integration/cli.js
index 1c5ce1a1f478a1831522203a6822d8093b04e448..3935e242b18451556b2a7bb8dfe6a9319fa1cf5f 100644
--- a/test/integration/cli.js
+++ b/test/integration/cli.js
@@ -6,7 +6,7 @@ const co        = require('co');
 const should    = require('should');
 const _         = require('underscore');
 const toolbox   = require('./tools/toolbox');
-const cli       = require('../../app/cli');
+const duniter   = require('../../index');
 const merkleh   = require('../../app/lib/helpers/merkle');
 const hashf     = require('../../app/lib/ucp/hashf');
 const constants = require('../../app/lib/constants');
@@ -161,9 +161,10 @@ describe("CLI", function() {
 function execute(args) {
   const finalArgs = [process.argv[0], __filename].concat(args).concat(['--mdb', DB_NAME]);
   return co(function*() {
-    const command = cli(finalArgs);
+
+    const stack = duniter.statics.autoStack();
     // Executes the command
-    return command.execute();
+    return stack.executeStack(finalArgs);
   });
 }