diff --git a/src/rust/amadeus-next/Cargo.toml b/src/rust/amadeus-next/Cargo.toml
index c6675a90c1242b1ff3a303f580caee3e22eaa7f8..d7be59e79b50a4e63369fd2623137536b44926b1 100644
--- a/src/rust/amadeus-next/Cargo.toml
+++ b/src/rust/amadeus-next/Cargo.toml
@@ -17,12 +17,14 @@ license = "MIT"
 [workspace.dependencies]
 log = "0.4"
 lazy_static = "1"
+toml = { version = "^0.5", features = ["preserve_order"] }
 serde = { version = "^1", default-features = false, features = [
     "std",
     "derive",
 ] }
-tokio = { version = "1", default-features = false, features = [
+tokio = { version = "1", features = [
     "rt",
+    "rt-multi-thread",
     "net",
     "time",
     "macros",
diff --git a/src/rust/amadeus-next/amalib/src/lib.rs b/src/rust/amadeus-next/amalib/src/lib.rs
index 33f96712e4480abb86ce1dd76c8371471a740197..4c5c6a933e1ce4625db715a5062d75748eedbc4f 100644
--- a/src/rust/amadeus-next/amalib/src/lib.rs
+++ b/src/rust/amadeus-next/amalib/src/lib.rs
@@ -16,6 +16,23 @@ pub use uri::*;
 pub(crate) use commons::{log::*, *};
 pub(crate) use std::str::FromStr;
 
+/// Whever the query uri should be only the direct query or must include the
+/// first links.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
+pub enum LektorQueryType {
+    /// Only direct results of the query.
+    DirectQuery,
+
+    /// Also includes the first links of the direct query.
+    QueryPlusLink,
+}
+
+impl Default for LektorQueryType {
+    fn default() -> Self {
+        LektorQueryType::QueryPlusLink
+    }
+}
+
 /// The playback state of the lektord server.
 #[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq, Copy)]
 pub enum LektorState {
diff --git a/src/rust/amadeus-next/amalib/src/query.rs b/src/rust/amadeus-next/amalib/src/query.rs
index bae0d763e9fa370f3db1c5e4827469352a511efd..fda3e8d16e3b3336411bbefb4a061d9e85c79d9f 100644
--- a/src/rust/amadeus-next/amalib/src/query.rs
+++ b/src/rust/amadeus-next/amalib/src/query.rs
@@ -33,6 +33,11 @@ pub enum LektorQuery {
     InsertKara(LektorUri),
     AddKara(LektorUri),
 
+    CreatePlaylist(String),
+    DestroyPlaylist(String),
+    AddToPlaylist(String, LektorUri),
+    RemoveFromPlaylist(String, LektorUri),
+
     Continuation(usize, Box<LektorQuery>),
 }
 
@@ -91,6 +96,11 @@ impl LektorQuery {
             Ping | Close | KillServer | PlayNext | PlayPrevious | ShuffleQueue | InsertKara(_)
             | AddKara(_) => LektorEmptyResponse::from_formated,
 
+            CreatePlaylist(_)
+            | DestroyPlaylist(_)
+            | AddToPlaylist(_, _)
+            | RemoveFromPlaylist(_, _) => LektorEmptyResponse::from_formated,
+
             ListAllPlaylists => LektorPlaylistSetResponse::from_formated,
             PlaybackStatus => LektorPlaybackStatusResponse::from_formated,
             CurrentKara => LektorCurrentKaraResponse::from_formated,
@@ -121,6 +131,13 @@ impl LektorQuery {
             | PlayNext | PlayPrevious | ShuffleQueue | ListAllPlaylists | ListPlaylist(_)
             | CurrentKara | Ping => Ok(()),
 
+            CreatePlaylist(str)
+            | DestroyPlaylist(str)
+            | AddToPlaylist(str, _)
+            | RemoveFromPlaylist(str, _) => {
+                either!(!str.is_empty() => Ok(()); Err("need to pass a non-empty string as playlist name".to_string()))
+            }
+
             // Should be admin commands
             Close => Err("close is an admin command".to_string()),
             KillServer => Err("kill server is an admin command".to_string()),
@@ -179,6 +196,11 @@ impl std::fmt::Display for LektorQuery {
             InsertKara(uri) => writeln!(f, "__insert {uri}"),
             AddKara(uri) => writeln!(f, "add {uri}"),
 
+            CreatePlaylist(name) => todo!("create playlist {name}"),
+            DestroyPlaylist(name) => todo!("destroy playlist {name}"),
+            AddToPlaylist(name, uri) => todo!("add to playlist {name} {uri}"),
+            RemoveFromPlaylist(name, uri) => todo!("remove from playlist {name} {uri}"),
+
             Continuation(cont, query) => write!(f, "{cont} {query}"),
         }
     }
diff --git a/src/rust/amadeus-next/amalib/src/uri.rs b/src/rust/amadeus-next/amalib/src/uri.rs
index c8fbc724145e1038e9ab6ac2135c8d478a6afdf0..a006c5779ef8c36a4cde04ca1eaf7df68e04b7df 100644
--- a/src/rust/amadeus-next/amalib/src/uri.rs
+++ b/src/rust/amadeus-next/amalib/src/uri.rs
@@ -3,27 +3,115 @@
 use crate::*;
 use std::str::FromStr;
 
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq, Eq)]
 pub enum LektorUri {
     Id(i32),
     Author(String),
     Playlist(String),
+    Language(String),
+    Type(String),
+    Origin(String),
     Query(Vec<String>),
+    QueryPlusLink(Vec<String>),
+}
+
+pub struct LektorUriBuilder<T: IntoIterator<Item = String> + Clone> {
+    query_ty: LektorQueryType,
+    strings: Option<T>,
+}
+
+impl<T: IntoIterator<Item = String> + Clone> Default for LektorUriBuilder<T> {
+    fn default() -> Self {
+        Self {
+            query_ty: LektorQueryType::QueryPlusLink,
+            strings: None,
+        }
+    }
+}
+
+impl<T: IntoIterator<Item = String> + Clone> LektorUriBuilder<T> {
+    pub fn query_type(mut self, ty: LektorQueryType) -> Self {
+        self.query_ty = ty;
+        self
+    }
+
+    pub fn strings(mut self, strings: T) -> Self {
+        self.strings = Some(strings);
+        self
+    }
+
+    /// Try to build an URI from user inputs.
+    pub fn try_build(self) -> Result<LektorUri, String> {
+        macro_rules! ret {
+            ($ty: ident, $var: expr) => {
+                return Ok(LektorUri::$ty($var))
+            };
+        }
+
+        if self.strings.is_none() || self.strings.clone().into_iter().flatten().count() == 0 {
+            return Err("no user input was passed, can't build an empty uri".to_string());
+        }
+
+        // First we try to see if the URI is an id.
+        let mut args = self.strings.clone().into_iter().flatten();
+        let first = args.next().ok_or_else(|| "invalid empty uri".to_string())?;
+        let next = args.next();
+        match (first.parse::<i32>(), &next) {
+            (Ok(id), None) if id > 0 => ret!(Id, id),
+            _ => {}
+        }
+
+        // Else we try to see if the URI is typed or not. Here we only permit
+        // one argument for the query (no space in names...).
+        match (&first[..], next, args.next()) {
+            ("author" | "auth", Some(str), None) => ret!(Author, str),
+            ("language" | "lang", Some(str), None) => ret!(Language, str),
+            ("playlist" | "plt", Some(str), None) => ret!(Playlist, str),
+            ("type", Some(str), None) => ret!(Type, str),
+            ("origin", Some(str), None) => ret!(Origin, str),
+            _ => {}
+        }
+
+        // We have a query. We check the config to see if we must build a simple
+        // query or a query with links.
+        let strs = self.strings.into_iter().flatten().collect();
+        match self.query_ty {
+            LektorQueryType::DirectQuery => ret!(Query, strs),
+            LektorQueryType::QueryPlusLink => ret!(QueryPlusLink, strs),
+        }
+    }
 }
 
 impl std::fmt::Display for LektorUri {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        macro_rules! uri {
+            ($uri: literal, $args: expr) => {
+                format!(concat!($uri, "://{}"), $args)
+            };
+        }
+
+        use LektorUri::*;
         let mut ret_str = match self {
-            Self::Id(id) => format!("id://{id}"),
-            Self::Author(author) => format!("author://{author}"),
-            Self::Playlist(plt_name) => format!("playlist://{plt_name}"),
-            Self::Query(sql_query) => {
+            Id(id) => uri!("id", id),
+            Type(ty) => uri!("type", ty),
+            Origin(origin) => uri!("origin", origin),
+            Author(author) => uri!("author", author),
+            Language(lang) => uri!("language", lang),
+            Playlist(name) => uri!("playlist", name),
+            Query(sql_query) => {
                 const MAX_ARGS: usize = crate::constants::LKT_MESSAGE_ARGS_MAX;
                 if sql_query.len() > MAX_ARGS {
                     warn!("the query will be truncated to {MAX_ARGS} arguments")
                 }
                 format!("query://%{}%", sql_query[..MAX_ARGS].join(" "))
             }
+            QueryPlusLink(sql_query) => {
+                const MAX_ARGS: usize = crate::constants::LKT_MESSAGE_ARGS_MAX;
+                if sql_query.len() > MAX_ARGS {
+                    warn!("the query will be truncated to {MAX_ARGS} arguments")
+                }
+                format!("query+link://%{}%", sql_query[..MAX_ARGS].join(" "))
+            }
         };
 
         const MAX_CHARS: usize = crate::constants::LKT_MESSAGE_MAX - 1;
@@ -39,6 +127,7 @@ impl std::fmt::Display for LektorUri {
 impl TryFrom<&str> for LektorUri {
     type Error = String;
 
+    /// Create an URI enum from the formated string
     fn try_from(value: &str) -> Result<Self, Self::Error> {
         match value.trim().split_once("://") {
             Some(("id", id)) => {
@@ -51,6 +140,9 @@ impl TryFrom<&str> for LektorUri {
             Some(("query", query)) => Ok(Self::Query(
                 query.split(' ').map(String::from).collect::<Vec<_>>(),
             )),
+            Some(("query+link", query)) => Ok(Self::Query(
+                query.split(' ').map(String::from).collect::<Vec<_>>(),
+            )),
             Some((identifier, _)) => Err(format!(
                 "the identifier `{identifier}` is not a valid uri identifier"
             )),
diff --git a/src/rust/amadeus-next/commons/Cargo.toml b/src/rust/amadeus-next/commons/Cargo.toml
index a76970b4340f88edf0b23d3b1a0abf509af3bb64..1e71a6fe965f8ee9dc614bb9c1acaf1ec3c335d8 100644
--- a/src/rust/amadeus-next/commons/Cargo.toml
+++ b/src/rust/amadeus-next/commons/Cargo.toml
@@ -7,6 +7,7 @@ license.workspace = true
 
 [dependencies]
 log.workspace = true
+lazy_static.workspace = true
 
 thiserror = { version = "^1", default-features = false }
 error-stack = { version = "^0.2", default-features = false, features = ["std"] }
diff --git a/src/rust/amadeus-next/commons/src/lib.rs b/src/rust/amadeus-next/commons/src/lib.rs
index 5322d4f839aa6bb9eaba7a2cb3dc1a65126802e7..7cd44cd06811bac7337380867fecf368915ace8f 100644
--- a/src/rust/amadeus-next/commons/src/lib.rs
+++ b/src/rust/amadeus-next/commons/src/lib.rs
@@ -3,5 +3,35 @@ mod error;
 mod macros;
 
 pub mod log;
+pub mod logger;
 
 pub use error::*;
+
+/// Returns the home folder of the user. If no home folder is found log the
+/// error and panic. This function check in order the following env variables to
+/// determine the home folder:
+/// 1. HOME
+/// 2. HOMEPATH
+/// 3. USERPROFILE
+pub fn user_home_directory() -> std::path::PathBuf {
+    use std::env::var;
+    std::path::PathBuf::from(if let Ok(home) = var("HOME") {
+        home
+    } else if let Ok(home) = var("HOMEPATH") {
+        home
+    } else if let Ok(home) = var("USERPROFILE") {
+        home
+    } else {
+        panic!("failed to find a home folder for the user...")
+    })
+}
+
+/// Returns the config folder for the user. If neither HOME, HOMEPATH,
+/// 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 {
+    let folder = user_home_directory().join(".config").join(app.as_ref());
+    std::fs::create_dir_all(&folder).expect("failed to create config folder for application");
+    folder
+}
diff --git a/src/rust/amadeus-next/commons/src/logger.rs b/src/rust/amadeus-next/commons/src/logger.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4887005a1c4d5fa81d88d095482c149f353134a2
--- /dev/null
+++ b/src/rust/amadeus-next/commons/src/logger.rs
@@ -0,0 +1,89 @@
+use lazy_static::lazy_static;
+use log::{Level, Metadata, Record, SetLoggerError};
+use std::sync::atomic::AtomicU8;
+
+struct SimpleLogger {
+    level: AtomicU8,
+}
+
+#[repr(transparent)]
+struct SimpleLoggerRef {
+    pub inner: SimpleLogger,
+}
+
+lazy_static! {
+    static ref LOGGER: SimpleLoggerRef = SimpleLoggerRef {
+        inner: SimpleLogger {
+            level: AtomicU8::new(0)
+        }
+    };
+}
+
+impl SimpleLogger {
+    fn write_str(lvl: char, content: &str) {
+        content
+            .lines()
+            .filter(|content| !content.trim().is_empty())
+            .for_each(|content| eprintln!("{lvl} {}", content));
+    }
+
+    fn level(&self) -> Level {
+        // Always display errors and warnings.
+        match self.level.load(std::sync::atomic::Ordering::SeqCst) {
+            0 => Level::Warn,
+            1 => Level::Info,
+            2 => Level::Debug,
+            _ => Level::Trace,
+        }
+    }
+}
+
+impl log::Log for SimpleLogger {
+    fn enabled(&self, metadata: &Metadata) -> bool {
+        metadata.level() <= self.level()
+    }
+
+    fn log(&self, record: &Record) {
+        if self.enabled(record.metadata()) {
+            let level = match record.level() {
+                Level::Error => '!',
+                Level::Warn => '*',
+                Level::Info => '#',
+                Level::Debug => '.',
+                Level::Trace => ' ',
+            };
+            if let Some(s) = record.args().as_str() {
+                SimpleLogger::write_str(level, s)
+            } else {
+                SimpleLogger::write_str(level, &record.args().to_string());
+            }
+        }
+    }
+
+    fn flush(&self) {}
+}
+
+pub fn level(lvl: u8) {
+    LOGGER
+        .inner
+        .level
+        .store(lvl, std::sync::atomic::Ordering::SeqCst);
+    log::set_max_level(LOGGER.inner.level().to_level_filter());
+}
+
+pub fn init(level: Option<Level>) -> Result<(), SetLoggerError> {
+    log::set_logger(&LOGGER.inner).map(|()| {
+        log::set_max_level(match level {
+            None => LOGGER.inner.level().to_level_filter(),
+            Some(level) => {
+                match level {
+                    Level::Trace => self::level(3),
+                    Level::Debug => self::level(2),
+                    Level::Info => self::level(1),
+                    Level::Warn | Level::Error => self::level(0),
+                };
+                level.to_level_filter()
+            }
+        });
+    })
+}
diff --git a/src/rust/amadeus-next/lkt-rs/Cargo.toml b/src/rust/amadeus-next/lkt-rs/Cargo.toml
index 2c5ad34a742179ad9eb332b1cb4fedaa74b194d4..c9757ad5717988d728efdca93f420a965b37222d 100644
--- a/src/rust/amadeus-next/lkt-rs/Cargo.toml
+++ b/src/rust/amadeus-next/lkt-rs/Cargo.toml
@@ -10,7 +10,9 @@ name = "lkt"
 path = "src/main.rs"
 
 [dependencies]
+tokio.workspace = true
 serde.workspace = true
+toml.workspace = true
 
 commons = { path = "../commons" }
 amalib = { path = "../amalib" }
diff --git a/src/rust/amadeus-next/lkt-rs/src/args.rs b/src/rust/amadeus-next/lkt-rs/src/args.rs
index c6971d9e2cfa28a4f3fcf7bdb39fd6d4d9c283ed..85518495950f5c7ad2f987b9009ee22cec8921ff 100644
--- a/src/rust/amadeus-next/lkt-rs/src/args.rs
+++ b/src/rust/amadeus-next/lkt-rs/src/args.rs
@@ -1,11 +1,10 @@
-use std::ops::Range;
-
-use crate::types::*;
+use crate::{config::LktConfig, types::*};
 use clap::{Parser, Subcommand};
+use std::ops::Range;
 
 #[derive(Parser, Debug)]
 #[command(author, version, about, long_about = None, disable_help_subcommand = true, args_conflicts_with_subcommands = true)]
-pub struct Args {
+struct Args {
     #[command(subcommand)]
     action: SubCommand,
 
@@ -19,81 +18,25 @@ pub struct Args {
 
 #[derive(Subcommand, Debug)]
 #[command(long_about = None, about = None)]
-pub enum SubCommand {
-    // Status commands
-    #[command(
-        about = "Prints informations about the currently playing kara. Can be used to display the current kara in a status bar like xmobar or in the i3 panel."
-    )]
-    Current,
-
-    #[command(
-        about = "Prints information about the state of lektor and the currently playing kara."
-    )]
-    Status,
-
-    // Playback commands
-    #[command(
-        about = "Toggle play/pause state. If the playback is stopped, start at a possibly specified index."
-    )]
-    Play { index: Option<usize> },
-
-    // Playlist commands
-    #[command(short_flag = 'P')]
-    Playlist {
-        #[arg( value_parser    = clap::builder::NonEmptyStringValueParser::new()
-             , exclusive       = true
-             , short           = 'c'
-             , long            = "create"
-             , help            = "Create a new playlist with a specific name."
-        )]
-        create: Option<String>,
-
-        #[arg( value_parser    = clap::builder::NonEmptyStringValueParser::new()
-             , exclusive       = true
-             , short           = 'd'
-             , long            = "destroy"
-             , help            = "Delete a playlist with all its content, do nothing if the playlist didn't exists."
-        )]
-        destroy: Option<String>,
-
-        #[arg( action          = clap::ArgAction::Set
-             , short           = 'l'
-             , long            = "list"
-             , exclusive       = true
-             , help            = "List the content of the playlist named if a name is passed. If missing returns a list of all the available playlists."
-        )]
-        list: Option<Option<String>>,
-
-        #[arg( action          = clap::ArgAction::Append
-             , exclusive       = true
-             , num_args        = 2..
-             , short           = 'r'
-             , long            = "remove"
-             , help            = "Deletes karas from a playlist with a valid query. The first element is the name of the playlist."
-        )]
-        remove: Option<Vec<String>>,
-
-        #[arg( action          = clap::ArgAction::Append
-             , exclusive       = true
-             , num_args        = 2..
-             , short           = 'a'
-             , long            = "add"
-             , help            = "Add karas to a playlist with a valid query. The first element is the name of the playlist."
-        )]
-        add: Option<Vec<String>>,
-    },
-
+enum SubCommand {
     // Queue commands
     #[command(short_flag = 'Q')]
     Queue {
-        #[arg( action    = clap::ArgAction::Set
+        #[arg( action    = clap::ArgAction::SetTrue
              , exclusive = true
-             , short     = 'S'
-             , long      = "seek"
-             , help      = "Goto to the kara with the specified id in the queue."
-             , id        = "ID"
+             , short     = 'I'
+             , long      = "current"
+             , help      = "Prints informations about the currently playing kara. Can be used to display the current kara in a status bar like xmobar or in the i3 panel."
         )]
-        seek: Option<usize>,
+        current: bool,
+
+        #[arg( action    = clap::ArgAction::SetTrue
+             , exclusive = true
+             , short     = 'i'
+             , long      = "info"
+             , help      = "Prints information about the state of lektor and the currently playing kara."
+        )]
+        status: bool,
 
         #[arg( action       = clap::ArgAction::Set
              , value_parser = crate::parsers::RangeParser::new()
@@ -103,33 +46,7 @@ pub enum SubCommand {
              , help         = "Prints the names and ids of karas in the queue. Karas are designated by a range of positions."
              , id           = "LOWER:UPPER"
         )]
-        pos: Option<Range<usize>>,
-
-        #[arg( action    = clap::ArgAction::SetTrue
-             , exclusive = true
-             , short     = 'C'
-             , long      = "crop"
-             , help      = "Crop the queue, delete every kara from it appart from the currently playing one."
-        )]
-        crop: bool,
-
-        #[arg( action    = clap::ArgAction::SetTrue
-             , exclusive = true
-             , short     = 'c'
-             , long      = "clear"
-             , help      = "Clear the queue and set the state to stopped."
-        )]
-        clear: bool,
-
-        #[arg( action    = clap::ArgAction::Set
-             , exclusive = true
-             , num_args  = 2
-             , short     = 's'
-             , long      = "swap"
-             , help      = "Swap two karas in the queue by their position"
-             , id        = "POS"
-        )]
-        swap: Option<Vec<String>>,
+        pos: Option<Option<Range<usize>>>,
 
         #[arg( action    = clap::ArgAction::SetTrue
              , exclusive = true
@@ -145,14 +62,22 @@ pub enum SubCommand {
              , long      = "previous"
              , help      = "Play the previous kara in the queue."
         )]
-        previous: bool,
+        prev: bool,
+
+        #[arg( action        = clap::ArgAction::Set
+             , exclusive     = true
+             , short         = 'r'
+             , long          = "play"
+             , help          = "Toggle play/pause state. If the playback is stopped, start at a possibly specified index."
+             , id            = "QUEUE_INDEX"
+        )]
+        play: Option<Option<usize>>,
 
         #[arg( action        = clap::ArgAction::Set
              , exclusive     = true
              , short         = 'P'
              , long          = "pause"
              , help          = "Toggle the play/pause state. If an argument is passed, it sets the pause state to the value, converted to boolean."
-             , default_value = None
         )]
         pause: Option<Option<bool>>,
 
@@ -164,16 +89,71 @@ pub enum SubCommand {
         )]
         stop: bool,
 
-        #[arg( action        = clap::ArgAction::Set
-             , default_value = PriorityLevel::Add
-             , exclusive     = true
-             , short         = 'z'
-             , long          = "shuffle"
-             , help          = concat!( "Shuffle the queue. If lektord was paused it will unpause but if it was stopped it won't start. "
-                                      , "If it exists, the current kara will be placed in the first place in the queue. "
-                                      , "You can also pass a level to shuffle up to. The level can be assed as a numeric or its string representation"
+        #[arg( action       = clap::ArgAction::Set
+             , exclusive    = true
+             , raw          = true
+             , last         = false
+             , num_args     = 1..
+             , short        = 'a'
+             , long         = "add"
+             , id           = "[PRIORITY] QUERY"
+             , help         = concat!( "Add karas to the queue when they are matching the query. "
+                                     , "The priority can be passed as a numeric or as its string representation. "
+                                     , "[default priority: 1, possible priorities: 1..5]"
         ))]
-        shuffle: Option<PriorityLevel>,
+        add: Option<Vec<String>>,
+
+        #[arg( action    = clap::ArgAction::SetTrue
+             , exclusive = true
+             , short     = 'C'
+             , long      = "crop"
+             , help      = "Crop the queue, delete every kara from it appart from the currently playing one."
+        )]
+        crop: bool,
+
+        #[arg( action                = clap::ArgAction::Set
+             , exclusive             = true
+             , short                 = 'c'
+             , long                  = "clear"
+             , id                    = "CLEAR_LVL"
+             , default_missing_value = PriorityLevel::Enforce
+             , help = concat!( "Clear the queue and set the state to stopped. "
+                             , "You can also pass a level to clear up to. The level can be passed as a numeric or as its string representation. "
+                             , "[default value: 5, possible values: 1..5]"
+        ))]
+        clear: Option<Option<PriorityLevel>>,
+
+        #[arg( action    = clap::ArgAction::Set
+             , exclusive = true
+             , num_args  = 2
+             , short     = 's'
+             , long      = "swap"
+             , help      = "Swap two karas in the queue by their position"
+             , id        = "POS"
+        )]
+        swap: Option<Vec<String>>,
+
+        #[arg( action    = clap::ArgAction::Set
+             , exclusive = true
+             , short     = 'S'
+             , long      = "seek"
+             , help      = "Goto to the kara with the specified id in the queue."
+             , id        = "ID"
+        )]
+        seek: Option<usize>,
+
+        #[arg( action                = clap::ArgAction::Set
+             , exclusive             = true
+             , short                 = 'z'
+             , long                  = "shuffle"
+             , id                    = "SHUFFLE_LVL"
+             , default_missing_value = PriorityLevel::Enforce
+             , help = concat!( "Shuffle the queue. If lektord was paused it will unpause but if it was stopped it won't start. "
+                             , "If it exists, the current kara will be placed in the first place in the queue. "
+                             , "You can also pass a level to shuffle up to. The level can be passed as a numeric or as its string representation. "
+                             , "[default value: 5, possible values: 1..5]"
+        ))]
+        shuffle: Option<Option<PriorityLevel>>,
     },
 
     // Search commands
@@ -232,6 +212,52 @@ pub enum SubCommand {
         queue: Option<Vec<String>>,
     },
 
+    // Playlist commands
+    #[command(short_flag = 'P')]
+    Playlist {
+        #[arg( value_parser    = clap::builder::NonEmptyStringValueParser::new()
+             , exclusive       = true
+             , short           = 'c'
+             , long            = "create"
+             , help            = "Create a new playlist with a specific name."
+        )]
+        create: Option<String>,
+
+        #[arg( value_parser    = clap::builder::NonEmptyStringValueParser::new()
+             , exclusive       = true
+             , short           = 'd'
+             , long            = "destroy"
+             , help            = "Delete a playlist with all its content, do nothing if the playlist didn't exists."
+        )]
+        destroy: Option<String>,
+
+        #[arg( action          = clap::ArgAction::Set
+             , short           = 'l'
+             , long            = "list"
+             , exclusive       = true
+             , help            = "List the content of the playlist named if a name is passed. If missing returns a list of all the available playlists."
+        )]
+        list: Option<Option<String>>,
+
+        #[arg( action          = clap::ArgAction::Append
+             , exclusive       = true
+             , num_args        = 2..
+             , short           = 'a'
+             , long            = "add"
+             , help            = "Add karas to a playlist with a valid query. The first element is the name of the playlist."
+        )]
+        add: Option<Vec<String>>,
+
+        #[arg( action          = clap::ArgAction::Append
+             , exclusive       = true
+             , num_args        = 2..
+             , short           = 'r'
+             , long            = "remove"
+             , help            = "Deletes karas from a playlist with a valid query. The first element is the name of the playlist."
+        )]
+        remove: Option<Vec<String>>,
+    },
+
     // Admin commands
     #[command(short_flag = 'A')]
     Admin {
@@ -263,19 +289,22 @@ pub enum SubCommand {
              , short  = 'u'
              , long   = "update"
              , group  = "action"
-             , help   = "Update the base from all the repos from the config file. Don't scan for new files in the filesystem."
-        )]
+             , help   = concat!( "Update the base from all the repos from the config file. Don't scan for new files in the filesystem. "
+                               , "With the --dry flag only fetch the information, don't download the kara from the repos."
+        ))]
         update: bool,
 
         #[arg( action = clap::ArgAction::SetTrue
              , short  = 'P'
              , long   = "populate"
              , group  = "action"
-             , help   = "Update the base from what is present on disk. The kara files and metadata files must be present on the disk."
-        )]
+             , help   = concat!( "Update the base from what is present on disk. The kara files and metadata files must be present on the disk. "
+                               , "With the --dry flag only add the found karas to the database, but don't mark them as downloaded."
+        ))]
         populate: bool,
 
         #[arg( action = clap::ArgAction::SetTrue
+             , short  = 'd'
              , long   = "dry"
              , help   = concat!( "Don't do the action. For update only fetch the metadata and not the karas. "
                                , "For the populate don't mark the karas as downloaded. This flag can't be "
@@ -284,3 +313,249 @@ pub enum SubCommand {
         dry: bool,
     },
 }
+
+/// The lkt command enum. The main should only handle that enum to decide what
+/// to do based on user input.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum LktCommand {
+    Queue(LktQueueCommand),
+    Search(LktSearchCommand),
+    Playlist(LktPlaylistCommand),
+    Admin(LktAdminCommand),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum LktQueueCommand {
+    ShowCurrent,
+
+    ShowStatus,
+
+    List {
+        range: Range<usize>,
+    },
+
+    Next,
+
+    Previous,
+
+    Play {
+        index: usize,
+    },
+
+    Pause,
+
+    UnPause,
+
+    TogglePause,
+
+    Stop,
+
+    Add {
+        priority: PriorityLevel,
+        query: amalib::LektorUri,
+    },
+
+    Crop,
+
+    Clear {
+        up_to_lvl: PriorityLevel,
+    },
+
+    SwapPositions {
+        p1: usize,
+        p2: usize,
+    },
+
+    SeekIdInQueue {
+        id: usize,
+    },
+
+    Shuffle {
+        up_to_lvl: PriorityLevel,
+    },
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum LktSearchCommand {
+    Database {
+        query: amalib::LektorUri,
+    },
+
+    DatabaseCount {
+        query: amalib::LektorUri,
+    },
+
+    Get {
+        id: usize,
+    },
+
+    Playlist {
+        name: String,
+        query: amalib::LektorUri,
+    },
+
+    Queue {
+        query: amalib::LektorUri,
+    },
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum LktPlaylistCommand {
+    Create {
+        name: String,
+    },
+
+    Destroy {
+        name: String,
+    },
+
+    Add {
+        name: String,
+        query: amalib::LektorUri,
+    },
+
+    Remove {
+        name: String,
+        query: amalib::LektorUri,
+    },
+
+    List,
+
+    ListContent {
+        name: String,
+    },
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum LktAdminCommand {
+    Ping,
+    Kill,
+    Restart,
+    Update { dry: bool },
+    Populate { dry: bool },
+}
+
+impl LktCommand {
+    pub fn parse(config: &LktConfig) -> Result<Self, String> {
+        let Args { action, verbose } = <Args as Parser>::parse();
+        commons::logger::level(verbose);
+
+        macro_rules! sterr {
+            ($($arg:tt)*) => {{
+                let res = std::fmt::format(format_args!($($arg)*));
+                return Err(res)
+            }};
+        }
+
+        use LktCommand::*;
+        #[rustfmt::skip]
+        let res = match action {
+            // QUEUE COMMANDS //
+
+            SubCommand::Queue { crop:    true, .. } => Queue(LktQueueCommand::Crop),
+            SubCommand::Queue { next:    true, .. } => Queue(LktQueueCommand::Next),
+            SubCommand::Queue { prev:    true, .. } => Queue(LktQueueCommand::Previous),
+            SubCommand::Queue { stop:    true, .. } => Queue(LktQueueCommand::Stop),
+            SubCommand::Queue { current: true, .. } => { Queue(LktQueueCommand::ShowCurrent) }
+            SubCommand::Queue { status:  true, .. } => { Queue(LktQueueCommand::ShowStatus) }
+            SubCommand::Queue { pause:   Some(None),        .. } => Queue(LktQueueCommand::TogglePause),
+            SubCommand::Queue { pause:   Some(Some(true)),  .. } => Queue(LktQueueCommand::Pause),
+            SubCommand::Queue { pause:   Some(Some(false)), .. } => Queue(LktQueueCommand::UnPause),
+            SubCommand::Queue { shuffle: Some(lvl), .. } => Queue(LktQueueCommand::Shuffle { up_to_lvl: lvl.unwrap_or(PriorityLevel::Enforce) }),
+            SubCommand::Queue { clear:   Some(lvl), .. } => Queue(LktQueueCommand::Clear   { up_to_lvl: lvl.unwrap_or(PriorityLevel::Enforce) }),
+            SubCommand::Queue { seek:    Some(id), .. } => { Queue(LktQueueCommand::SeekIdInQueue { id }) }
+            SubCommand::Queue { pos:     Some(range), .. } => Queue(LktQueueCommand::List { range: range.unwrap_or_else(|| 0..usize::MAX) }),
+            SubCommand::Queue { play:    Some(index), .. } => Queue(LktQueueCommand::Play { index: index.unwrap_or_default() }),
+            SubCommand::Queue { swap:    Some(args), .. } => match &args[..] {
+                [p1, p2] => Queue(LktQueueCommand::SwapPositions {
+                    p1: p1.parse::<usize>().map_err(|e| format!("invalid swap command, {p1} is not an integer: {e}"))?,
+                    p2: p2.parse::<usize>().map_err(|e| format!("invalid swap command, {p2} is not an integer: {e}"))?
+                }),
+                _ => sterr!("invalid arguments passed to the queue swap command, expected two positions, got: {args:?}")
+            }
+            SubCommand::Queue { add: Some(args), .. } => match &args[..] {
+                [] => sterr!("invalid queue add command, expected `priority query...` or `query...`, got nothing"),
+                [priority, query @ .. ] if !query.is_empty() && priority.parse::<PriorityLevel>().is_ok() => Queue(LktQueueCommand::Add {
+                    priority: priority.parse().expect("something went wrong"),
+                    query: amalib::LektorUriBuilder::default()
+                        .query_type(config.search.query_type).strings(query.iter().cloned()).try_build()
+                        .map_err(|e| format!("invalid query in queue add command: {e}"))?
+                }),
+                query => Queue(LktQueueCommand::Add { priority: PriorityLevel::Add,
+                    query: amalib::LektorUriBuilder::default()
+                        .query_type(config.search.query_type).strings(query.iter().cloned()).try_build()
+                        .map_err(|e| format!("invalid query in queue add command: {e}"))?
+                }),
+            }
+
+            // SEARCH COMMANDS //
+
+            SubCommand::Search { database: Some(query), .. } => Search(LktSearchCommand::Database {
+                query: amalib::LektorUriBuilder::default()
+                    .query_type(config.search.query_type).strings(query.into_iter()).try_build()
+                    .map_err(|e| format!("invalid query in search database command: {e}"))?
+            }),
+            SubCommand::Search { count: Some(query), .. } => Search(LktSearchCommand::DatabaseCount { query:
+                amalib::LektorUriBuilder::default()
+                    .query_type(config.search.query_type).strings(query.into_iter()).try_build()
+                    .map_err(|e| format!("invalid query in search count command: {e}"))?
+            }),
+            SubCommand::Search { queue: Some(query), .. } => Search(LktSearchCommand::Queue { query:
+                amalib::LektorUriBuilder::default()
+                    .query_type(config.search.query_type).strings(query.into_iter()).try_build()
+                    .map_err(|e| format!("invalid query in search queue command: {e}"))?
+            }),
+            SubCommand::Search { get: Some(id), .. } => Search(LktSearchCommand::Get { id }),
+            SubCommand::Search { plt: Some(args), .. } => match &args[..] {
+                [name, query @ ..] if !query.is_empty() => Search(LktSearchCommand::Playlist { name: name.clone(), query:
+                    amalib::LektorUriBuilder::default()
+                        .query_type(config.search.query_type).strings(query.iter().cloned()).try_build()
+                        .map_err(|e| format!("invalid query in search playlist command: {e}"))?
+                }),
+                [] => sterr!("invalid search playlist command, expected `plt-name query...`, got nothing"),
+                _  => sterr!("invalid search playlist command, expected `plt-name query...`, got: {args:?}"),
+            }
+
+            // PLAYLIST COMMANDS //
+
+            SubCommand::Playlist { list:    Some(None), .. } => Playlist(LktPlaylistCommand::List),
+            SubCommand::Playlist { list:    Some(Some(name)), .. } => Playlist(LktPlaylistCommand::ListContent { name }),
+            SubCommand::Playlist { create:  Some(name), ..  }      => Playlist(LktPlaylistCommand::Create { name }),
+            SubCommand::Playlist { destroy: Some(name), ..  }      => Playlist(LktPlaylistCommand::Destroy { name }),
+            SubCommand::Playlist { add:     Some(args), ..  } => match &args[..] {
+                [name, query @ ..] if !query.is_empty() => Playlist(LktPlaylistCommand::Add { name: name.clone(), query:
+                    amalib::LektorUriBuilder::default()
+                        .query_type(config.search.query_type).strings(query.iter().cloned()).try_build()
+                        .map_err(|e| format!("invalid query in playlist add command: {e}"))?
+                }),
+                [] => sterr!("invalid playlist add command, expected `plt-name query...`, got nothing"),
+                _ =>  sterr!("invalid playlist add command, expected `plt-name query...`, got: {args:?}"),
+            }
+            SubCommand::Playlist { remove: Some(args), .. } => match &args[..] {
+                [name, query @ ..] if !query.is_empty() => Playlist(LktPlaylistCommand::Remove { name: name.clone(), query:
+                    amalib::LektorUriBuilder::default()
+                        .query_type(config.search.query_type).strings(query.iter().cloned()).try_build()
+                        .map_err(|e| format!("invalid query in playlist add command: {e}"))?
+                }),
+                [] => sterr!("invalid playlist add command, expected `plt-name query...`, got nothing"),
+                _ =>  sterr!("invalid playlist remove command, expected `plt-name query...`, got: {args:?}",),
+            }
+
+            // ADMIN COMMANDS //
+
+            SubCommand::Admin { ping:     true, .. } => Admin(LktAdminCommand::Ping),
+            SubCommand::Admin { kill:     true, .. } => Admin(LktAdminCommand::Kill),
+            SubCommand::Admin { restart:  true, .. } => Admin(LktAdminCommand::Restart),
+            SubCommand::Admin { update:   true, dry, .. } => Admin(LktAdminCommand::Update   { dry }),
+            SubCommand::Admin { populate: true, dry, .. } => Admin(LktAdminCommand::Populate { dry }),
+
+            // FALLBACKS //
+
+            SubCommand::Queue    { .. } => sterr!("invalid queue command! check the help messages to see what are valid commands"),
+            SubCommand::Search   { .. } => sterr!("invalid search command! check the help messages to see what are valid commands"),
+            SubCommand::Playlist { .. } => sterr!("invalid playlist command! check the help messages to see what are valid commands"),
+            SubCommand::Admin    { .. } => sterr!("invalid admin command! check the help messages to see what are valid commands"),
+        };
+        Ok(res)
+    }
+}
diff --git a/src/rust/amadeus-next/lkt-rs/src/config.rs b/src/rust/amadeus-next/lkt-rs/src/config.rs
new file mode 100644
index 0000000000000000000000000000000000000000..d0ea7f9cee29a710a93a57604d3d2ea68d51c0c5
--- /dev/null
+++ b/src/rust/amadeus-next/lkt-rs/src/config.rs
@@ -0,0 +1,96 @@
+use amalib::LektorQueryType;
+
+#[derive(Debug, serde::Deserialize, serde::Serialize)]
+#[serde(tag = "type", content = "port")]
+#[allow(clippy::upper_case_acronyms)]
+pub enum LktHostPort {
+    UNIX,
+    TCP(i16),
+}
+
+#[derive(Debug, serde::Deserialize, serde::Serialize)]
+pub struct LktAdminConfig {
+    pub user: Option<String>,
+    pub password: Option<String>,
+}
+
+#[derive(Debug, serde::Deserialize, serde::Serialize, Default)]
+pub struct LktSearchConfig {
+    pub query_type: LektorQueryType,
+}
+
+#[derive(Debug, serde::Deserialize, serde::Serialize)]
+pub struct LktHostConfig {
+    pub address: String,
+    pub socket: LktHostPort,
+}
+
+#[derive(Debug, serde::Deserialize, serde::Serialize)]
+pub struct LktConfig {
+    pub host: LktHostConfig,
+    pub admin: Option<LktAdminConfig>,
+    pub search: LktSearchConfig,
+}
+
+impl Default for LktConfig {
+    fn default() -> Self {
+        Self {
+            host: Default::default(),
+            admin: Some(Default::default()),
+            search: Default::default(),
+        }
+    }
+}
+
+impl Default for LktHostPort {
+    fn default() -> Self {
+        LktHostPort::TCP(6600)
+    }
+}
+
+impl Default for LktAdminConfig {
+    fn default() -> Self {
+        Self {
+            user: Some("sakura".to_string()),
+            password: Some("sakura".to_string()),
+        }
+    }
+}
+
+impl Default for LktHostConfig {
+    fn default() -> Self {
+        Self {
+            address: "127.0.0.1".to_string(),
+            socket: Default::default(),
+        }
+    }
+}
+
+impl LktConfig {
+    pub fn has_valid_admin_config(&self) -> bool {
+        matches!(&self.admin, Some(LktAdminConfig {
+                user: Some(user),
+                password: Some(password),
+            }) if !user.trim().is_empty() && !password.trim().is_empty())
+    }
+}
+
+pub fn get_or_write_default() -> Result<LktConfig, String> {
+    let path = commons::user_config_directory("amadeus").join("lkt.toml");
+    match std::fs::read_to_string(&path) {
+        Ok(config) => toml::from_str(&config).map_err(|err| {
+            let path = path.to_string_lossy();
+            format!("invalid config file `{path}`: {err}")
+        }),
+        Err(_) => {
+            let default_config = LktConfig::default();
+            let pretty_config = toml::to_string_pretty(&default_config)
+                .expect("failed to prettify the default config...");
+            std::fs::write(&path, pretty_config).map_err(|err| {
+                let path = path.to_string_lossy();
+                format!("failed to write default config to file `{path}`: {err}")
+            })?;
+            Ok(default_config)
+        }
+    }
+}
diff --git a/src/rust/amadeus-next/lkt-rs/src/main.rs b/src/rust/amadeus-next/lkt-rs/src/main.rs
index 943affe76681d30fa437d2c90ea9f9a3706d0e86..382301334c7426646f0c0a6b609f9163a893a7de 100644
--- a/src/rust/amadeus-next/lkt-rs/src/main.rs
+++ b/src/rust/amadeus-next/lkt-rs/src/main.rs
@@ -1,8 +1,105 @@
 mod args;
+mod config;
 mod parsers;
 mod types;
 
-fn main() {
-    let args = <args::Args as clap::Parser>::parse();
-    println!("{args:#?}");
+#[tokio::main(worker_threads = 2)]
+async fn main() {
+    commons::Report::install_debug_hook::<std::backtrace::Backtrace>(|_, _| {});
+    commons::logger::init(Some(commons::log::Level::Trace))
+        .unwrap_or_else(|e| panic!("failed to install logger: {e}"));
+    let config = config::get_or_write_default().expect("failed to get or write default config");
+
+    let cmd = args::LktCommand::parse(&config).expect("oupsy");
+    handle_cmd(config, cmd).await
+}
+
+async fn handle_cmd(config: config::LktConfig, cmd: args::LktCommand) {
+    commons::log::debug!("{config:#?}\ncmd = {cmd:#?}");
+    let conn = match config.host.socket {
+        config::LktHostPort::UNIX => unimplemented!("connexion to unix socket is not implemented"),
+        config::LktHostPort::TCP(port) => amalib::LektorConnexion::new(&config.host.address, port)
+            .await
+            .expect("failed to connect to the lektord server"),
+    };
+    match cmd {
+        args::LktCommand::Queue(cmd) => handle_cmd_queue(config, conn, cmd).await,
+        args::LktCommand::Search(cmd) => handle_cmd_search(config, conn, cmd).await,
+        args::LktCommand::Playlist(cmd) => handle_cmd_playlist(config, conn, cmd).await,
+        args::LktCommand::Admin(cmd) => handle_cmd_admin(config, conn, cmd).await,
+    }
+}
+
+macro_rules! send {
+    ($conn: expr => $query: expr ; $expect: pat_param => $action: block) => {
+        match $conn.send($query).await.expect("failed to execute command") {
+            $expect => $action,
+            _ => panic!("invalid response type for command"),
+        }
+    };
+
+    ($conn: expr => $query: expr ; ok) => {{
+        let _ = $conn.send($query).await.expect("failed to execute command");
+    }};
+}
+
+async fn handle_cmd_queue(
+    _: config::LktConfig,
+    _: amalib::LektorConnexion,
+    _: args::LktQueueCommand,
+) {
+    unimplemented!()
+}
+
+async fn handle_cmd_search(
+    _: config::LktConfig,
+    _: amalib::LektorConnexion,
+    _: args::LktSearchCommand,
+) {
+    unimplemented!()
+}
+
+async fn handle_cmd_playlist(
+    _: config::LktConfig,
+    mut conn: amalib::LektorConnexion,
+    cmd: args::LktPlaylistCommand,
+) {
+    match cmd {
+        args::LktPlaylistCommand::Create { name } => {
+            send!(conn => amalib::LektorQuery::CreatePlaylist(name); ok)
+        }
+        args::LktPlaylistCommand::Destroy { name } => {
+            send!(conn => amalib::LektorQuery::DestroyPlaylist(name); ok)
+        }
+        args::LktPlaylistCommand::Add { name, query } => {
+            send!(conn => amalib::LektorQuery::AddToPlaylist(name, query); ok)
+        }
+        args::LktPlaylistCommand::Remove { name, query } => {
+            send!(conn => amalib::LektorQuery::RemoveFromPlaylist(name, query); ok)
+        }
+        args::LktPlaylistCommand::List => {
+            send!(conn => amalib::LektorQuery::ListAllPlaylists
+                ; amalib::LektorResponse::PlaylistSet(playlists) => {
+                    playlists.into_iter().for_each(|plt| println!("{plt}"))
+            })
+        }
+        args::LktPlaylistCommand::ListContent { name } => {
+            send!(conn => amalib::LektorQuery::ListPlaylist(name)
+                ; amalib::LektorResponse::KaraSet(karas) => {
+                    karas.into_iter().for_each(|kara| println!("{kara}"))
+            })
+        }
+    }
+}
+
+async fn handle_cmd_admin(
+    config: config::LktConfig,
+    _: amalib::LektorConnexion,
+    cmd: args::LktAdminCommand,
+) {
+    if config.has_valid_admin_config() {
+        unimplemented!("handle admin command: {cmd:#?}")
+    } else {
+        panic!("no admin config block in config file")
+    }
 }
diff --git a/src/rust/amadeus-next/lkt-rs/src/types.rs b/src/rust/amadeus-next/lkt-rs/src/types.rs
index 70c63816d573cd136ddde3b5a499f670aa549df5..834e99ddd2698b1fcf5c69f0230795c659d8f735 100644
--- a/src/rust/amadeus-next/lkt-rs/src/types.rs
+++ b/src/rust/amadeus-next/lkt-rs/src/types.rs
@@ -1,4 +1,4 @@
-use commons::fatal;
+use std::str::FromStr;
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 pub enum PriorityLevel {
@@ -9,17 +9,25 @@ pub enum PriorityLevel {
     Enforce = 5,
 }
 
-impl From<&str> for PriorityLevel {
-    fn from(value: &str) -> Self {
+impl std::str::FromStr for PriorityLevel {
+    type Err = String;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
         use PriorityLevel::*;
-        match value {
+        Ok(match s {
             "add" | "1" => Add,
             "suggest" | "2" => Suggest,
             "insert" | "3" => Insert,
             "pick" | "4" => Pick,
             "enforce" | "5" => Enforce,
-            _ => fatal!("unknown priority level: {value}"),
-        }
+            _ => return Err(format!("unknown priority level: {s}")),
+        })
+    }
+}
+
+impl From<&str> for PriorityLevel {
+    fn from(value: &str) -> Self {
+        PriorityLevel::from_str(value).expect("conversion failed")
     }
 }