diff --git a/Cargo.lock b/Cargo.lock
index ac60184105728b4360734684a19465dc70ff0406..9710fe5a6efc1d02ac7a5e181ca9065480e44c6a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1992,6 +1992,7 @@ dependencies = [
  "anyhow",
  "async-trait",
  "axum",
+ "clap",
  "futures",
  "hyper",
  "lektor_mpris",
diff --git a/lektor_utils/src/config/base.rs b/lektor_utils/src/config/base.rs
index 8e95d67ec8c9f31c3ed3cc9636f561d075d374f4..0207bef0a8a5075823f885b3d206c5b20f005411 100644
--- a/lektor_utils/src/config/base.rs
+++ b/lektor_utils/src/config/base.rs
@@ -35,6 +35,8 @@ pub struct LektorPlayerConfig {
     pub font_size: u64,
     pub font_name: String,
     pub msg_duration: u64,
+
+    #[cfg(unix)]
     pub force_x11: bool,
 }
 
diff --git a/lektor_utils/src/config/mod.rs b/lektor_utils/src/config/mod.rs
index 7a0a22adeb33b03c738b977fb38fd4482ba83fbb..78ed93498f59506a6715ae81a4a17f96673cb962 100644
--- a/lektor_utils/src/config/mod.rs
+++ b/lektor_utils/src/config/mod.rs
@@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize};
 pub fn get_or_write_default_config<Config: Default + Serialize + for<'de> Deserialize<'de>>(
     appli: &'static str,
 ) -> Result<Config> {
-    let path = crate::user_config_directory("lektor").join(format!("{appli}.toml"));
+    let path = crate::user_config_directory_sync("lektor").join(format!("{appli}.toml"));
     match std::fs::read_to_string(&path) {
         Ok(config) => toml::from_str::<Config>(&config)
             .with_context(|| format!("invalid config file `{}`", path.to_string_lossy())),
@@ -47,3 +47,19 @@ pub async fn write_config_async<Config: Default + Serialize + for<'de> Deseriali
             )
         })
 }
+
+/// Write the config to its file
+pub fn write_config_sync<Config: Default + Serialize + for<'de> Deserialize<'de>>(
+    appli: &'static str,
+    config: Config,
+) -> Result<()> {
+    let path = crate::user_config_directory_sync("lektor").join(format!("{appli}.toml"));
+    let pretty_config =
+        toml::to_string_pretty(&config).expect("failed to prettify the default config...");
+    std::fs::write(&path, pretty_config).with_context(|| {
+        format!(
+            "failed to write default config to file `{}`",
+            path.to_string_lossy()
+        )
+    })
+}
diff --git a/lektor_utils/src/lib.rs b/lektor_utils/src/lib.rs
index ce8d14b283d64a1acf86c29f7188bc19e8145948..abfd8fbb223c7e7986bbd2eae9a459d89fd9679e 100644
--- a/lektor_utils/src/lib.rs
+++ b/lektor_utils/src/lib.rs
@@ -40,7 +40,7 @@ pub fn user_home_directory() -> std::path::PathBuf {
 /// USERPROFILE are defined this function will panic. The config directory is
 /// named `.config` and is placed in the home folder. This is the most common
 /// behaviour on unix systems.
-pub fn user_config_directory(app: impl AsRef<str>) -> std::path::PathBuf {
+pub fn user_config_directory_sync(app: impl AsRef<str>) -> std::path::PathBuf {
     let folder = user_home_directory().join(".config").join(app.as_ref());
     std::fs::create_dir_all(&folder).unwrap_or_else(|err| {
         panic!(
@@ -51,7 +51,7 @@ pub fn user_config_directory(app: impl AsRef<str>) -> std::path::PathBuf {
     folder
 }
 
-/// Same as [user_config_directory] but with async using tokio.
+/// Same as [user_config_directory_sync] but with async using tokio.
 pub async fn user_config_directory_async(app: impl AsRef<str>) -> std::path::PathBuf {
     let folder = user_home_directory().join(".config").join(app.as_ref());
     tokio::fs::create_dir_all(&folder)
diff --git a/lektord/Cargo.toml b/lektord/Cargo.toml
index b22fcff071228ca8474f7092991c16b7c736a4b6..79a6ffe14872327d36831da1bf5359b7123569da 100644
--- a/lektord/Cargo.toml
+++ b/lektord/Cargo.toml
@@ -20,6 +20,8 @@ tokio.workspace = true
 hyper.workspace = true
 async-trait.workspace = true
 
+clap.workspace = true
+
 lektor_nkdb = { path = "../lektor_nkdb" }
 lektor_repo = { path = "../lektor_repo" }
 lektor_utils = { path = "../lektor_utils" }
diff --git a/lektord/src/app.rs b/lektord/src/app.rs
index a1f76f1717db5ab439ce389e81923a084a1ee1d7..a1bbe7788f3d2aa58e4a6b958f2c1c414f37e0b2 100644
--- a/lektord/src/app.rs
+++ b/lektord/src/app.rs
@@ -162,7 +162,6 @@ pub(crate) type LektorStatePtr = Arc<LektorState>;
 impl LektorState {
     /// Create a new server state from the configuration file.
     pub async fn new(config: LektorConfig, shutdown: Sender<()>) -> Result<LektorStatePtr> {
-        lektor_utils::logger::level(config.log);
         let LektorConfig {
             database: LektorDatabaseConfig { folder, .. },
             player,
diff --git a/lektord/src/cmd.rs b/lektord/src/cmd.rs
new file mode 100644
index 0000000000000000000000000000000000000000..144e395852cf3c2710d298d226bece4f63894bf4
--- /dev/null
+++ b/lektord/src/cmd.rs
@@ -0,0 +1,54 @@
+use clap::{Parser, Subcommand};
+use lektor_utils::*;
+
+#[derive(Parser, Debug, Default)]
+#[command( author
+         , version = version()
+         , about
+         , long_about = None
+         , disable_help_subcommand = false
+         , args_conflicts_with_subcommands = true
+)]
+pub struct Args {
+    #[command(subcommand)]
+    pub action: Option<SubCommand>,
+
+    /// Make lkt more verbose, repeat to make it even more verbose.
+    #[arg( long
+         , short  = 'v'
+         , action = clap::ArgAction::Count
+    )]
+    pub verbose: u8,
+}
+
+#[derive(Subcommand, Debug, Default)]
+#[command(long_about = None, about = None)]
+pub enum SubCommand {
+    /// Launch the daemon, this is the default command, if nothing is passed then the daemon will
+    /// be unleashed ;)
+    #[command(short_flag = 'S')]
+    #[default]
+    Start,
+
+    /// Config manipulation commands, to not edit it by hand!
+    #[command(short_flag = 'C', arg_required_else_help = true)]
+    Config {
+        /// Show the current config.
+        #[arg( action    = clap::ArgAction::SetTrue
+             , exclusive = true
+             , short     = 's'
+             , long      = "show"
+        )]
+        show: bool,
+
+        /// Edit the config.
+        #[arg( action     = clap::ArgAction::Set
+             , exclusive  = true
+             , num_args   = 2
+             , short      = 'e'
+             , long       = "edit"
+             , value_name = "<OPT.NAME> <VALUE>"
+        )]
+        edit: Option<Vec<String>>,
+    },
+}
diff --git a/lektord/src/config.rs b/lektord/src/config.rs
index b211e3ebe3421609de6f4f4ad72caea767f0ffc7..09406eb0dea99c49afc69e431342eb76690e6c76 100644
--- a/lektord/src/config.rs
+++ b/lektord/src/config.rs
@@ -1,6 +1,7 @@
+use anyhow::{bail, Context, Result};
 use lektor_utils::{config::*, log::Level as LogLevel};
 use serde::{Deserialize, Serialize};
-use std::net::SocketAddr;
+use std::{net::SocketAddr, path::PathBuf};
 
 /// Lektord configuration.
 #[derive(Debug, Serialize, Deserialize, Clone)]
@@ -41,3 +42,111 @@ impl Default for LektorConfig {
         }
     }
 }
+
+impl LektorConfig {
+    pub fn edit(mut self, name: impl AsRef<str>, value: impl AsRef<str>) -> Result<()> {
+        match &name.as_ref().split('.').collect::<Vec<_>>()[..] {
+            // Flat
+            ["log"] => todo!(),
+            ["workers"] => {
+                self.workers = value
+                    .as_ref()
+                    .parse::<usize>()
+                    .with_context(|| "invalid value for 'workers'")?
+            }
+            ["mpris"] => {
+                self.mpris = value
+                    .as_ref()
+                    .parse::<bool>()
+                    .with_context(|| "invalid value for 'mpris'")?
+            }
+
+            // Database
+            ["database", "folder"] => self.database.folder = PathBuf::from(value.as_ref()),
+            ["database", "autoclear"] => {
+                self.database.autoclear = value
+                    .as_ref()
+                    .parse::<bool>()
+                    .with_context(|| "invalid value for 'database.autoclear'")?
+            }
+            ["database", "save_history"] => {
+                self.database.save_history = value
+                    .as_ref()
+                    .parse::<bool>()
+                    .with_context(|| "invalid value for 'database.save_history'")?
+            }
+
+            // Player
+            ["player", "font_name"] => self.player.font_name = value.as_ref().to_string(),
+            ["player", "font_size"] => {
+                self.player.font_size = value
+                    .as_ref()
+                    .parse::<u64>()
+                    .with_context(|| "invalid value for 'player.font_size'")?
+            }
+            ["player", "msg_duration"] => {
+                self.player.msg_duration = value
+                    .as_ref()
+                    .parse::<u64>()
+                    .with_context(|| "invalid value for 'player.msg_duration'")?
+            }
+            #[cfg(unix)]
+            ["player", "force_x11"] => {
+                self.player.force_x11 = value
+                    .as_ref()
+                    .parse::<bool>()
+                    .with_context(|| "invalid value for 'player.force_x11'")?
+            }
+
+            //
+            _ => bail!("invalid option name {}", name.as_ref()),
+        }
+        write_config_sync("lektord", self)
+    }
+}
+
+impl std::fmt::Display for LektorConfig {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        writeln!(f, "log:     {}", self.log)?;
+        writeln!(f, "mpris:   {}", self.mpris)?;
+        writeln!(f, "workers: {}", self.workers)?;
+
+        writeln!(f, "\n[users]")?;
+        for UserConfig { user, token, admin } in &self.users {
+            let title = match admin {
+                true => "admin:",
+                false => "user: ",
+            };
+            writeln!(f, "{title} {user} -> {token}")?;
+        }
+
+        writeln!(f, "\n[listen]")?;
+        for (idx, addr) in self.listen.iter().enumerate() {
+            writeln!(f, "{idx}: {addr}")?;
+        }
+
+        writeln!(f, "\n[database]")?;
+        let folder = self.database.folder.to_string_lossy();
+        writeln!(f, "folder:       {}", folder)?;
+        writeln!(f, "autoclear:    {}", self.database.autoclear)?;
+        writeln!(f, "save_history: {}", self.database.save_history)?;
+
+        writeln!(f, "\n[player]")?;
+        writeln!(f, "font_name:    {}", self.player.font_name)?;
+        writeln!(f, "font_size:    {}", self.player.font_size)?;
+        writeln!(f, "msg_duration: {}", self.player.msg_duration)?;
+        #[cfg(unix)]
+        writeln!(f, "force_x11:    {}", self.player.force_x11)?;
+
+        for repo in &self.repo {
+            writeln!(f, "\n[repo.{}]", repo.name)?;
+            writeln!(f, "api:   {:?}", repo.api)?;
+            writeln!(f, "token: {}", repo.token)?;
+            for (idx, url) in repo.urls.iter().enumerate() {
+                writeln!(f, "url.{idx}: {url}")?;
+            }
+        }
+
+        Ok(())
+    }
+}
diff --git a/lektord/src/main.rs b/lektord/src/main.rs
index 27e362c8bf7d24b5d1c0d8926c9dad74190fd0a2..91bcc2d607f7f31ee181b73a8e72446c6c1178f7 100644
--- a/lektord/src/main.rs
+++ b/lektord/src/main.rs
@@ -1,5 +1,6 @@
 mod app;
 pub mod c_wrapper;
+mod cmd;
 mod config;
 mod error;
 mod listen;
@@ -12,6 +13,7 @@ pub use error::*;
 pub use listen::*;
 
 use anyhow::{Context, Result};
+use cmd::SubCommand;
 use lektor_utils::*;
 use std::sync::atomic::{AtomicU64, Ordering};
 use tokio::{signal, sync::oneshot::Receiver};
@@ -19,28 +21,52 @@ use tokio::{signal, sync::oneshot::Receiver};
 fn main() -> Result<()> {
     logger::init(Some(log::Level::Debug)).expect("failed to install logger");
     let config = lektor_utils::config::get_or_write_default_config::<LektorConfig>("lektord")?;
-    tokio::runtime::Builder::new_multi_thread()
-        .worker_threads(config.workers) // Thread count from the config file.
-        .thread_name_fn(|| {
-            static COUNT: AtomicU64 = AtomicU64::new(0);
-            format!("lektord-{}", COUNT.fetch_add(1, Ordering::SeqCst))
-        }) // Custom names, to see utilization... and because it's fancy!
-        .enable_all() // Don't care, enable everything.
-        .thread_stack_size(3 * 1024 * 1024) // 3Mio for each thread, should be enaugh
-        .max_blocking_threads(1024)
-        .build()?
-        .block_on(async move {
-            lektor_utils::config::write_config_async("lektord", config.clone()).await?; // Write to apply changes...
-            let addrs = AddrIncomingCombined::from_iter(&config.listen).err()?;
-            let (app, shutdown) = app(config)
-                .await
-                .with_context(|| "failed to build service")?;
-            hyper::Server::builder(addrs)
-                .serve(app.into_make_service())
-                .with_graceful_shutdown(shutdown_signal(shutdown))
-                .await?;
+    let args = <cmd::Args as clap::Parser>::parse();
+    lektor_utils::logger::level(config.log);
+    if args.verbose != 0 {
+        lektor_utils::logger::level_int(args.verbose);
+    }
+
+    match args.action.unwrap_or_default() {
+        SubCommand::Config { show: true, .. } => {
+            println!("{config}");
             Ok(())
-        })
+        }
+
+        SubCommand::Config {
+            edit: Some(args), ..
+        } => match &args[..] {
+            [name, value] => config.edit(name, value),
+            args => unreachable!("{args:#?}"),
+        },
+
+        SubCommand::Start => {
+            log::info!("starting the lektord daemon");
+            tokio::runtime::Builder::new_multi_thread()
+                .worker_threads(config.workers) // Thread count from the config file.
+                .thread_name_fn(|| {
+                    static COUNT: AtomicU64 = AtomicU64::new(0);
+                    format!("lektord-{}", COUNT.fetch_add(1, Ordering::SeqCst))
+                })
+                .enable_all()
+                .thread_stack_size(3 * 1024 * 1024) // 3Mio for each thread, should be enaugh
+                .build()?
+                .block_on(async move {
+                    lektor_utils::config::write_config_async("lektord", config.clone()).await?; // Write to apply changes...
+                    let addrs = AddrIncomingCombined::from_iter(&config.listen).err()?;
+                    let (app, shutdown) = app(config)
+                        .await
+                        .with_context(|| "failed to build service")?;
+                    hyper::Server::builder(addrs)
+                        .serve(app.into_make_service())
+                        .with_graceful_shutdown(shutdown_signal(shutdown))
+                        .await?;
+                    Ok(())
+                })
+        }
+
+        args => unreachable!("{args:?}"),
+    }
 }
 
 /// Gracefull ctrl+c handling.